// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Text; using System.Windows.Forms; using System.Drawing; using System.Drawing.Drawing2D; using System.Diagnostics; namespace MemoryProfiler2 { /// /// Memory bitmap parser /// Helper class used to parse memory profiler snapshots and displaying them in the graphical form. /// public static class FMemoryBitmapParser { /// Left margin for the memory bitmap image. private const int MEMORY_BITMAP_LEFT_MARGIN = 70; /// Bitmap which contains image of the memory bitmap. private static Bitmap MemoryBitmap; /// Amount of memory allocated for the current view of memory bitmap. private static long AllocatedMemorySize; /// Indicates how many bytes are displayed as a single pixel. private static int BytesPerPixel = 1; /// Selected pointer range types. enum ESelectionTypes { /// Selected pointer begins before the currently visible memory range. ST_PointerBeforeBase, /// Selected pointer is in range of the currently visible memory range. ST_PointerInRange, /// Selected pointer is outside range of the currently visible memory range. ST_PointerOutRange, /// Selected pointer ends after the currently visible memory range. ST_PointerAfterTop, } /// Start position of the currently selected memory region. private static Point SelectedMemoryStart; /// End position of the currently selected memory region. private static Point SelectedMemoryEnd; /// Address of the first allocated pointer. private static ulong MemoryBase; /// Size of the allocated memory for all memory pools. private static ulong MemorySize; /// True if we are selecting a memory region to zoom. private static bool bZoomSelectionInProgress; /// Start position of the currently selected memory region. private static int ZoomSelectionStartY; /// Start position of the currently selected memory region. private static int ZoomSelectionEndY; /// True if we are in a zoomed memory region. private static bool bZoomSelectionActive; /// Address of the first allocated pointer for currently zoomed memory region. private static ulong ZoomSelectionMemoryBase; /// Size of the allocated memory for for currently zoomed memory region. private static ulong ZoomSelectionMemorySize; /// Array of all zoom levels. private static List ZoomLevels = new List(); private static Pen HeatPen = new Pen( Color.FromArgb( 10, 255, 255, 255 ) ); private static Brush CheckerBWBrush4x4 = new HatchBrush( HatchStyle.LargeCheckerBoard, Color.FromArgb( 160, Color.Plum ), Color.FromArgb( 160, Color.SkyBlue ) ); private static Brush CheckerBWBrush2x2 = new HatchBrush( HatchStyle.SmallCheckerBoard, Color.FromArgb( 192, Color.Plum ), Color.FromArgb( 192, Color.SkyBlue ) ); private static Brush CheckerBWBrush1x1 = new HatchBrush( HatchStyle.Percent50, Color.FromArgb( 224, Color.Plum ), Color.FromArgb( 224, Color.SkyBlue ) ); private static Pen OutlinePen1 = new Pen( Color.FromArgb( 128, Color.White ) ); private static Pen OutlinePen2 = new Pen( Color.FromArgb( 192, Color.Black ) ); /// Pen with 1x1 checker used to draw selected memory region. private static Pen CheckerPen1x1 = new Pen( CheckerBWBrush1x1 ); /// Pen with 2x2 checker used to draw selected memory region. private static Pen CheckerPen2x2 = new Pen( CheckerBWBrush2x2 ); /// Pen with 4x4 checker used to draw selected memory region. private static Pen CheckerPen4x4 = new Pen( CheckerBWBrush4x4 ); private static Pen SelectedSolidPen = new Pen( new SolidBrush( Color.LightSteelBlue ) ); /// Pen used to draw the selected memory region. Size of the checker is based on the selection height. private static Pen SelectedMemoryPen { get { int SelectionHeight = Math.Abs( SelectedMemoryStart.Y - SelectedMemoryEnd.Y ); if( SelectionHeight < 8 ) { return SelectedSolidPen/*CheckerPen1x1*/; } else if( SelectionHeight < 16 ) { return CheckerPen2x2; } return CheckerPen4x4; } } /// Pen used to draw the zoom selection. Size of the checker is based on the selection height. private static Pen ZoomMemoryPen { get { int SelectionHeight = Math.Abs( ZoomSelectionEndY - ZoomSelectionStartY ); if( SelectionHeight < 8 ) { return CheckerPen1x1; } else if( SelectionHeight < 16 ) { return CheckerPen2x2; } return CheckerPen4x4; } } /// /// Note that the magenta area at the bottom of the graph is where the /// memory space ends. Because I chose to force a whole number of bytes per /// pixel in the bitmap, there will almost always be a few pixels at the end /// of the bitmap that fall outside the selected memory range. These are /// coloured magenta. /// private static Pen OutOfSpacePen = new Pen( Color.DarkMagenta ); /// Reference to the main memory profiler window. private static MainWindow OwnerWindow; /// Sets reference to the main memory profiler window. public static void SetProfilerWindow( MainWindow InMainWindow ) { OwnerWindow = InMainWindow; } public static Bitmap ParseSnapshot( int BitmapWidth, int BitmapHeight, ulong SnapshotStreamIndex, ulong DiffbaseSnapshotStreamIndex, string FilterText ) { // Progress bar. OwnerWindow.ToolStripProgressBar.Value = 0; OwnerWindow.ToolStripProgressBar.Visible = true; AllocatedMemorySize = 0; Bitmap MyBitmap = new Bitmap( BitmapWidth, BitmapHeight ); Graphics MyGraphics = Graphics.FromImage( MyBitmap ); int PixelCount = BitmapWidth * BitmapHeight; BytesPerPixel = ( int )Math.Ceiling( ( double )MemorySize / ( double )PixelCount ); long BytesPerLine = ( long )BytesPerPixel * ( long )BitmapWidth; ulong MemoryTop = MemoryBase + MemorySize; // JarekS@TODO Multithreading if( OwnerWindow.MemoryBitmapHeatMapButton.Checked ) { // clear bitmap to black MyGraphics.FillRectangle( Brushes.Black, 0, 0, BitmapWidth, BitmapHeight ); if( DiffbaseSnapshotStreamIndex == FStreamInfo.INVALID_STREAM_INDEX ) { // if there is no base snapshot, start at the beginning of the profile DiffbaseSnapshotStreamIndex = 0; } using( FScopedLogTimer ParseTiming = new FScopedLogTimer( "FMemoryBitmapParser.ParseSnapshot.HeatMap" ) ) { long ProgressInterval = FStreamInfo.GlobalInstance.CallStackArray.Count / 20; long NextProgressUpdate = ProgressInterval; int CallStackCurrent = 0; foreach( FCallStack CallStack in FStreamInfo.GlobalInstance.CallStackArray ) { // Update progress bar. if( CallStackCurrent >= NextProgressUpdate ) { OwnerWindow.ToolStripProgressBar.PerformStep(); NextProgressUpdate += ProgressInterval; Debug.WriteLine( "FMemoryBitmapParser.ParseSnapshot.HeatMap " + OwnerWindow.ToolStripProgressBar.Value + "/20" ); } CallStackCurrent++; if( CallStack.RunFilters( FilterText, OwnerWindow.Options.ClassGroups, OwnerWindow.IsFilteringIn(), OwnerWindow.SelectedMemoryPool ) ) { foreach( FAllocationLifecycle AllocLifecycle in CallStack.CompleteLifecycles ) { if( AllocLifecycle.AllocEvent.StreamIndex < SnapshotStreamIndex ) { if( AllocLifecycle.AllocEvent.StreamIndex > DiffbaseSnapshotStreamIndex && AllocLifecycle.AllocEvent.Pointer + ( ulong )AllocLifecycle.AllocEvent.Size > MemoryBase && AllocLifecycle.AllocEvent.Pointer < MemoryTop ) { FillMemoryArea( MyGraphics, HeatPen, BitmapWidth, BytesPerLine, BytesPerPixel, AllocLifecycle.AllocEvent.Pointer - MemoryBase, AllocLifecycle.AllocEvent.Size ); } if( AllocLifecycle.ReallocsEvents != null ) { for( int i = 0; i < AllocLifecycle.ReallocsEvents.Count; i++ ) { FReallocationEvent ReallocEvent = AllocLifecycle.ReallocsEvents[ i ]; if( ReallocEvent.StreamIndex < SnapshotStreamIndex ) { if( ReallocEvent.StreamIndex > DiffbaseSnapshotStreamIndex && ReallocEvent.NewPointer + ( ulong )ReallocEvent.NewSize > MemoryBase && ReallocEvent.NewPointer < MemoryTop ) { FillMemoryArea( MyGraphics, HeatPen, BitmapWidth, BytesPerLine, BytesPerPixel, ReallocEvent.NewPointer - MemoryBase, ReallocEvent.NewSize ); } } else { break; } } } } } } } } } else { bool bDiffAllocs = DiffbaseSnapshotStreamIndex != FStreamInfo.INVALID_STREAM_INDEX; long ProgressIntervalCallstack = FStreamInfo.GlobalInstance.CallStackArray.Count / ( bDiffAllocs ? 5 : 10 ); long ProgressIntervalGroup = OwnerWindow.Options.ClassGroups.Count / 10; long NextProgressUpdate = ProgressIntervalCallstack; int CallStackCurrent = 0; using( FScopedLogTimer ParseTiming = new FScopedLogTimer( "FMemoryBitmapParser.ParseSnapshot.AllAllocations" ) ) { // render all allocations in black foreach( FCallStack CallStack in FStreamInfo.GlobalInstance.CallStackArray ) { // Update progress bar. if( CallStackCurrent >= NextProgressUpdate ) { OwnerWindow.ToolStripProgressBar.PerformStep(); NextProgressUpdate += ProgressIntervalCallstack; Debug.WriteLine( "FMemoryBitmapParser.ParseSnapshot " + OwnerWindow.ToolStripProgressBar.Value + "/20" ); } CallStackCurrent++; if( CallStack.RunFilters( FilterText, OwnerWindow.Options.ClassGroups, OwnerWindow.IsFilteringIn(), OwnerWindow.SelectedMemoryPool ) ) { // Process complete life cycles. foreach( FAllocationLifecycle AllocLifecycle in CallStack.CompleteLifecycles ) { ProcessLifecycle( Pens.Black, BitmapWidth, SnapshotStreamIndex, MyGraphics, BytesPerLine, AllocLifecycle, 1 ); } // Process incomplete life cycles. foreach( KeyValuePair AllocLifecycle in CallStack.IncompleteLifecycles ) { ProcessLifecycle( Pens.Black, BitmapWidth, SnapshotStreamIndex, MyGraphics, BytesPerLine, AllocLifecycle.Value, 1 ); } } } } using( FScopedLogTimer ParseTiming = new FScopedLogTimer( "FMemoryBitmapParser.ParseSnapshot.HistogramAllocations" ) ) { int CurrentGroup = 0; long NextGroupProgressUpdate = ProgressIntervalGroup; // render histogram allocations in colour foreach( ClassGroup CallStackGroup in OwnerWindow.Options.ClassGroups ) { // Update progress bar. if( CurrentGroup >= NextGroupProgressUpdate ) { OwnerWindow.ToolStripProgressBar.PerformStep(); NextGroupProgressUpdate += ProgressIntervalGroup; Debug.WriteLine( "FMemoryBitmapParser.ParseSnapshot " + OwnerWindow.ToolStripProgressBar.Value + "/20" ); } CurrentGroup++; Pen GroupPen = new Pen( CallStackGroup.Color ); foreach( CallStackPattern CallStackPatternIt in CallStackGroup.CallStackPatterns ) { foreach( FCallStack CallStack in CallStackPatternIt.GetCallStacks() ) { if( CallStack.RunFilters( FilterText, OwnerWindow.Options.ClassGroups, OwnerWindow.IsFilteringIn(), OwnerWindow.SelectedMemoryPool ) ) { foreach( FAllocationLifecycle AllocLifecycle in CallStack.CompleteLifecycles ) { ProcessLifecycle( GroupPen, BitmapWidth, SnapshotStreamIndex, MyGraphics, BytesPerLine, AllocLifecycle, 0 ); } foreach (KeyValuePair AllocLifecycle in CallStack.IncompleteLifecycles) { ProcessLifecycle( GroupPen, BitmapWidth, SnapshotStreamIndex, MyGraphics, BytesPerLine, AllocLifecycle.Value, 0 ); } } } } GroupPen.Dispose(); } } using( FScopedLogTimer ParseTiming = new FScopedLogTimer( "FMemoryBitmapParser.ParseSnapshot.DiffAllocations" ) ) { CallStackCurrent = 0; NextProgressUpdate = ProgressIntervalCallstack; // render white where allocation exists in diff-base snapshot if( DiffbaseSnapshotStreamIndex != FStreamInfo.INVALID_STREAM_INDEX ) { foreach( FCallStack CallStack in FStreamInfo.GlobalInstance.CallStackArray ) { // Update progress bar. if( CallStackCurrent >= NextProgressUpdate ) { OwnerWindow.ToolStripProgressBar.PerformStep(); NextProgressUpdate += ProgressIntervalCallstack; Debug.WriteLine( "FMemoryBitmapParser.ParseSnapshot " + OwnerWindow.ToolStripProgressBar.Value + "/20" ); } CallStackCurrent++; if( CallStack.RunFilters( FilterText, OwnerWindow.Options.ClassGroups, OwnerWindow.IsFilteringIn(), OwnerWindow.SelectedMemoryPool ) ) { foreach( FAllocationLifecycle AllocLifecycle in CallStack.CompleteLifecycles ) { ProcessLifecycle( Pens.AliceBlue, BitmapWidth, DiffbaseSnapshotStreamIndex, MyGraphics, BytesPerLine, AllocLifecycle, -1 ); } foreach( KeyValuePair AllocLifecycle in CallStack.IncompleteLifecycles ) { ProcessLifecycle( Pens.AliceBlue, BitmapWidth, DiffbaseSnapshotStreamIndex, MyGraphics, BytesPerLine, AllocLifecycle.Value, -1 ); } } } } } } // shade space at bottom of image to mark end of memory space. ulong MemoryTopPointer = MemorySize; int StartX = ( int )( MemoryTopPointer % ( uint )BytesPerLine / ( uint )BytesPerPixel ); int StartY = ( int )( MemoryTopPointer / ( uint )BytesPerLine ); MyGraphics.DrawLine( OutOfSpacePen, StartX, StartY, BitmapWidth - 1, StartY ); if( StartY + 1 < BitmapHeight ) { MyGraphics.FillRectangle( OutOfSpacePen.Brush, 0, StartY + 1, BitmapWidth, BitmapHeight - ( StartY + 1 ) ); } MyGraphics.Dispose(); OwnerWindow.ToolStripProgressBar.Visible = false; return MyBitmap; } private static void ProcessLifecycle( Pen AreaPen, int BitmapWidth, ulong SnapshotStreamIndex, Graphics MyGraphics, long BytesPerLine, FAllocationLifecycle AllocLifecycle, int MemorySizeAdjustment ) { uint Size = 0; ulong Pointer = AllocLifecycle.GetPointerAtStreamIndex( SnapshotStreamIndex, out Size ); if( Pointer != 0 && Pointer < MemoryBase + MemorySize && Pointer + Size >= MemoryBase ) { if( Pointer < MemoryBase ) { Size -= ( uint )( MemoryBase - Pointer ); Pointer = MemoryBase; } if( Pointer + Size > MemoryBase + MemorySize ) { Size = ( uint )( MemoryBase + MemorySize - Pointer ); } FillMemoryArea( MyGraphics, AreaPen, BitmapWidth, BytesPerLine, BytesPerPixel, Pointer - MemoryBase, ( int )Size ); AllocatedMemorySize += MemorySizeAdjustment * Size; } } public static ulong GetPointerFromPixel( int BitmapWidth, int BytesPerPixel, int X, int Y ) { long BytesPerLine = ( long )BytesPerPixel * ( long )BitmapWidth; Debug.Assert( BytesPerLine > 0 ); return MemoryBase + ( ulong )Y * ( ulong )BytesPerLine + ( ulong )X * ( ulong )BytesPerPixel; } // Not used. public static Point GetPixelFromPointer( int BitmapWidth, int BitmapHeight, int BytesPerPixel, ulong Pointer ) { long BytesPerLine = ( long )BytesPerPixel * ( long )BitmapWidth; bool bPointerBeforeBase = Pointer < MemoryBase; Pointer = Pointer < MemoryBase ? 0 : Pointer - MemoryBase; Point Calculated = new Point( ( int )( Pointer % ( ulong )BytesPerLine / ( ulong )BytesPerPixel ), ( int )( Pointer / ( ulong )BytesPerLine ) ); Calculated.Y = Math.Min( Calculated.Y, BitmapHeight-1 ); return Calculated; } public static void GetPixelRangeFromPointer( int BitmapWidth, int BitmapHeight, int BytesPerPixel, ulong Pointer, ulong PointerSize ) { ulong BytesPerLine = ( ulong )BytesPerPixel * ( ulong )BitmapWidth; ulong MemoryTop = MemoryBase + MemorySize; ulong PointerBase = Pointer - MemoryBase; ulong PointerTop = Pointer + PointerSize - MemoryBase; bool bPointerBeforeBase = Pointer < MemoryBase; bool bPointerAfterTop = Pointer + PointerSize > MemoryTop; SelectedMemoryStart = new Point( ( int )( PointerBase % BytesPerLine / ( ulong )BytesPerPixel ), ( int )( PointerBase / BytesPerLine ) ); SelectedMemoryStart.X += MEMORY_BITMAP_LEFT_MARGIN; SelectedMemoryEnd = new Point( ( int )( PointerTop % BytesPerLine / ( ulong )BytesPerPixel ), ( int )( PointerTop / BytesPerLine ) ); SelectedMemoryEnd.X += MEMORY_BITMAP_LEFT_MARGIN; if( bPointerBeforeBase ) { SelectedMemoryStart.Y = 0; } if( bPointerAfterTop ) { SelectedMemoryEnd.Y = BitmapHeight - 1; } OwnerWindow.MemoryBitmapResetSelectionButton.Enabled = true; } private static void FillMemoryArea( Graphics graphics, Pen pen, int BitmapWidth, long BytesPerLine, int BytesPerPixel, ulong Pointer, long Size ) { int StartX = ( int )( Pointer % ( ulong )BytesPerLine / ( ulong )BytesPerPixel ); int StartY = ( int )( Pointer / ( ulong )BytesPerLine ); int EndX = ( int )( ( Pointer + ( ulong )Size ) % ( ulong )BytesPerLine / ( ulong )BytesPerPixel ); int EndY = ( int )( ( Pointer + ( ulong )Size ) / ( ulong )BytesPerLine ); FillMemoryArea( graphics, pen, BitmapWidth, StartX, StartY, EndX, EndY, 0 ); } public static void FillMemoryArea( Graphics graphics, Pen pen, int BitmapWidth, int StartX, int StartY, int EndX, int EndY, int MarginWidth ) { Debug.Assert( StartY >= 0 ); Debug.Assert( EndY >= 0 ); if( StartY == EndY ) { graphics.DrawLine( pen, StartX, StartY, EndX, EndY ); } else { graphics.DrawLine( pen, StartX, StartY, MarginWidth + BitmapWidth - 1, StartY ); if( StartY + 1 < EndY ) { graphics.FillRectangle( pen.Brush, MarginWidth, StartY + 1, BitmapWidth, EndY - ( StartY + 1 ) ); } graphics.DrawLine( pen, MarginWidth, EndY, EndX, EndY ); } } public static void UndoLastZoom() { // button should be unclickable if this isn't the case Debug.Assert( ZoomLevels.Count > 0 ); if( ZoomLevels.Count > 1 ) { ZoomSelectionMemoryBase = ZoomLevels[ ZoomLevels.Count - 2 ].Base; ZoomSelectionMemorySize = ZoomLevels[ ZoomLevels.Count - 2 ].Size; bZoomSelectionActive = true; ZoomLevels.RemoveAt( ZoomLevels.Count - 1 ); } else { bZoomSelectionActive = false; ZoomLevels.Clear(); } if( ZoomLevels.Count == 0 ) { OwnerWindow.MemoryBitmapUndoZoomButton.Enabled = false; OwnerWindow.MemoryBitmapResetButton.Enabled = false; } RefreshMemoryBitmap(); } public static void ResetZoom() { bZoomSelectionActive = false; ZoomLevels.Clear(); OwnerWindow.MemoryBitmapUndoZoomButton.Enabled = false; OwnerWindow.MemoryBitmapResetButton.Enabled = false; RefreshMemoryBitmap(); } public static void ResetSelection() { OwnerWindow.MemoryBitmapCallStackListView.Items.Clear(); OwnerWindow.MemoryBitmapAllocationHistoryListView.SelectedItems.Clear(); OwnerWindow.MemoryBitmapAllocationHistoryListView.Items.Clear(); OwnerWindow.MemoryBitmapResetSelectionButton.Enabled = false; OwnerWindow.MemoryBitmapMemorySpaceLabel.Text = ""; OwnerWindow.MemoryBitmapSpaceSizeLabel.Text = ""; OwnerWindow.MemoryBitmapAllocatedMemoryLabel.Text = ""; // Refresh panel to clear selection. OwnerWindow.MemoryBitmapPanel.Invalidate(); } public static void RefreshMemoryBitmap() { if ( OwnerWindow == null || OwnerWindow.CurrentSnapshot == null || !FStreamInfo.GlobalInstance.CreationOptions.KeepLifecyclesCheckBox.Checked || OwnerWindow.WindowState == FormWindowState.Minimized ) { return; } if( MemoryBitmap != null ) { MemoryBitmap.Dispose(); MemoryBitmap = null; } if( bZoomSelectionActive ) { MemoryBase = ZoomSelectionMemoryBase; MemorySize = ZoomSelectionMemorySize; } else { MemoryBase = ulong.MaxValue; ulong MemoryTop = ulong.MinValue; bool bAtLeastOnePoolSelected = false; bool bAtLeastOneAllocation = false; foreach( EMemoryPool MemoryPool in FMemoryPoolInfo.GetMemoryPoolEnumerable() ) { if( ( ( OwnerWindow.SelectedMemoryPool & MemoryPool ) != EMemoryPool.MEMPOOL_None ) == OwnerWindow.IsFilteringIn() ) { bAtLeastOnePoolSelected = true; FMemoryPoolInfo MemPoolInfo = FStreamInfo.GlobalInstance.MemoryPoolInfo[ MemoryPool ]; if( !MemPoolInfo.IsEmpty ) { bAtLeastOneAllocation = true; MemoryBase = Math.Min( MemPoolInfo.MemoryBottom, MemoryBase ); MemoryTop = Math.Max( MemPoolInfo.MemoryTop, MemoryTop ); } } } if( !bAtLeastOnePoolSelected ) { MessageBox.Show( "All memory pools are being filtered out. No data to display." ); return; } if( !bAtLeastOneAllocation ) { MessageBox.Show( "The selected memory pools were never allocated to in this profiling run." ); return; } MemorySize = MemoryTop - MemoryBase; } ulong DiffbaseSnapshotStreamIndex = FStreamInfo.INVALID_STREAM_INDEX; ulong CurrentSnapshotStreamIndex = OwnerWindow.CurrentSnapshot.StreamIndex; if( OwnerWindow.CurrentSnapshot.bIsDiffResult ) { if( OwnerWindow.DiffStartComboBox.SelectedIndex == 0 ) { DiffbaseSnapshotStreamIndex = 0; } else { DiffbaseSnapshotStreamIndex = FStreamInfo.GlobalInstance.SnapshotList[ OwnerWindow.DiffStartComboBox.SelectedIndex - 1 ].StreamIndex; } if( OwnerWindow.DiffEndComboBox.SelectedIndex == 0 ) { CurrentSnapshotStreamIndex = 0; } else { CurrentSnapshotStreamIndex = FStreamInfo.GlobalInstance.SnapshotList[ OwnerWindow.DiffEndComboBox.SelectedIndex - 1 ].StreamIndex; } } OwnerWindow.SetWaitCursor( true ); MemoryBitmap = ParseSnapshot( OwnerWindow.MemoryBitmapPanel.ClientRectangle.Width - MEMORY_BITMAP_LEFT_MARGIN, OwnerWindow.MemoryBitmapPanel.ClientRectangle.Height, CurrentSnapshotStreamIndex, DiffbaseSnapshotStreamIndex, OwnerWindow.FilterTextBox.Text ); OwnerWindow.SetWaitCursor( false ); if( MemoryBitmap != null ) { OwnerWindow.MemoryBitmapMemorySpaceLabel.Text = "0x" + MemoryBase.ToString( "x16" ) + " - 0x" + ( MemoryBase + MemorySize - 1 ).ToString( "x16" ); OwnerWindow.MemoryBitmapSpaceSizeLabel.Text = MainWindow.FormatSizeString( ( long )MemorySize ); OwnerWindow.MemoryBitmapAllocatedMemoryLabel.Text = MainWindow.FormatSizeString( AllocatedMemorySize ); } else { OwnerWindow.MemoryBitmapMemorySpaceLabel.Text = "0x00000000 - 0x00000000"; OwnerWindow.MemoryBitmapSpaceSizeLabel.Text = "0 bytes"; OwnerWindow.MemoryBitmapAllocatedMemoryLabel.Text = "0 bytes"; } OwnerWindow.MemoryBitmapBytesPerPixelLabel.Text = BytesPerPixel.ToString(); OwnerWindow.MemoryBitmapAllocationHistoryListView.Items.Clear(); OwnerWindow.MemoryBitmapCallStackListView.Items.Clear(); OwnerWindow.MemoryBitmapPanel.Invalidate(); } public static void ClearView() { if( MemoryBitmap != null ) { MemoryBitmap.Dispose(); MemoryBitmap = null; } OwnerWindow.MemoryBitmapCallStackListView.BeginUpdate(); OwnerWindow.MemoryBitmapCallStackListView.SelectedItems.Clear(); OwnerWindow.MemoryBitmapCallStackListView.Items.Clear(); OwnerWindow.MemoryBitmapCallStackListView.EndUpdate(); OwnerWindow.MemoryBitmapAllocationHistoryListView.BeginUpdate(); OwnerWindow.MemoryBitmapAllocationHistoryListView.SelectedItems.Clear(); OwnerWindow.MemoryBitmapAllocationHistoryListView.Items.Clear(); OwnerWindow.MemoryBitmapAllocationHistoryListView.EndUpdate(); } public static void PaintPanel( PaintEventArgs e ) { if( MemoryBitmap != null ) { e.Graphics.DrawImageUnscaled( MemoryBitmap, MEMORY_BITMAP_LEFT_MARGIN, 0 ); e.Graphics.PixelOffsetMode = PixelOffsetMode.None; // Draw overlay for the currently selected memory region. if( OwnerWindow.MemoryBitmapAllocationHistoryListView.SelectedItems.Count > 0 ) { FillMemoryArea( e.Graphics, SelectedMemoryPen, MemoryBitmap.Width, SelectedMemoryStart.X, SelectedMemoryStart.Y, SelectedMemoryEnd.X, SelectedMemoryEnd.Y, MEMORY_BITMAP_LEFT_MARGIN ); // Draw border around the region to make that region more visible. if( SelectedMemoryStart.Y == SelectedMemoryEnd.Y ) { e.Graphics.DrawRectangle( OutlinePen1, SelectedMemoryStart.X - 4, SelectedMemoryStart.Y - 4, SelectedMemoryEnd.X - SelectedMemoryStart.X + 8, 8 ); e.Graphics.DrawRectangle( OutlinePen2, SelectedMemoryStart.X - 5, SelectedMemoryStart.Y - 5, SelectedMemoryEnd.X - SelectedMemoryStart.X + 10, 10 ); } /*else if( SelectedMemoryEnd.Y - SelectedMemoryStart.Y <= 3 ) { int SelHeight = SelectedMemoryEnd.Y - SelectedMemoryStart.Y; e.Graphics.DrawRectangle( OutlinePen1, MEMORY_BITMAP_LEFT_MARGIN, SelectedMemoryStart.Y - 4, MemoryBitmap.Width-2, SelHeight + 8 ); e.Graphics.DrawRectangle( OutlinePen2, MEMORY_BITMAP_LEFT_MARGIN, SelectedMemoryStart.Y - 5, MemoryBitmap.Width-1, SelHeight + 10 ); }*/ } if( bZoomSelectionInProgress ) { int Height = Math.Abs( ZoomSelectionEndY - ZoomSelectionStartY ) + 1; int Top = Math.Min( ZoomSelectionStartY, ZoomSelectionEndY ); e.Graphics.FillRectangle( ZoomMemoryPen.Brush, MEMORY_BITMAP_LEFT_MARGIN, Top, MemoryBitmap.Width, Height ); } long BytesPerLine = ( long )BytesPerPixel * ( long )MemoryBitmap.Width; float AxisHeight = ( long )BytesPerLine * ( long )MemoryBitmap.Height; string YAxisLabel; long MajorTick; MainWindow.SetUpAxisLabel( 1, ref AxisHeight, out YAxisLabel, out MajorTick ); // make sure that minorTick divides into majorTick without remainder MajorTick = MajorTick / 4 * 4; long MinorTick = MajorTick / 4; OwnerWindow.DrawYAxis( e.Graphics, Pens.Black, Color.Black, MajorTick, 9, MinorTick, 5, MEMORY_BITMAP_LEFT_MARGIN - 2, 0, MemoryBitmap.Height, 0, MemoryBitmap.Height, YAxisLabel, AxisHeight, true, true ); } } public static void UnsafeBitmapClick( MouseEventArgs e ) { OwnerWindow.MemoryBitmapCallStackListView.Items.Clear(); OwnerWindow.MemoryBitmapAllocationHistoryListView.BeginUpdate(); OwnerWindow.MemoryBitmapAllocationHistoryListView.Items.Clear(); ulong PointerFromPixel = FMemoryBitmapParser.GetPointerFromPixel( MemoryBitmap.Width, BytesPerPixel, e.X - MEMORY_BITMAP_LEFT_MARGIN, e.Y ); string FilterText = OwnerWindow.FilterTextBox.Text.ToUpperInvariant(); using( FScopedLogTimer ParseTiming = new FScopedLogTimer( "FMemoryBitmapParser.UnsafeBitmapClick" ) ) { foreach( FCallStack CallStack in FStreamInfo.GlobalInstance.CallStackArray ) { if( CallStack.RunFilters( FilterText, OwnerWindow.Options.ClassGroups, OwnerWindow.IsFilteringIn(), OwnerWindow.SelectedMemoryPool ) ) { foreach( FAllocationLifecycle AllocLifecycle in CallStack.CompleteLifecycles ) { ProcessLifecycleForPixel( PointerFromPixel, CallStack, AllocLifecycle, true ); } foreach( KeyValuePair AllocLifecycle in CallStack.IncompleteLifecycles ) { ProcessLifecycleForPixel( PointerFromPixel, CallStack, AllocLifecycle.Value, false ); } } } } // Pointers that were malloced and then realloced by a different callstack will have missing end frames // (marked with "-1"), but those end frames are guaranteed to be the same as the start frame of the // following allocation at this pointer, so we can just go over the list and fix up the references. for( int ItemIndex = 0; ItemIndex < OwnerWindow.MemoryBitmapAllocationHistoryListView.Items.Count; ItemIndex++ ) { if( OwnerWindow.MemoryBitmapAllocationHistoryListView.Items[ ItemIndex ].SubItems.Count > 0 && OwnerWindow.MemoryBitmapAllocationHistoryListView.Items[ ItemIndex ].SubItems[ 1 ].Text == "-1" && ItemIndex + 1 < OwnerWindow.MemoryBitmapAllocationHistoryListView.Items.Count ) { OwnerWindow.MemoryBitmapAllocationHistoryListView.Items[ ItemIndex ].SubItems[ 1 ].Text = OwnerWindow.MemoryBitmapAllocationHistoryListView.Items[ ItemIndex + 1 ].Text; } } OwnerWindow.MemoryBitmapAllocationHistoryListView.EndUpdate(); if( OwnerWindow.MemoryBitmapAllocationHistoryListView.Items.Count > 0 ) { OwnerWindow.MemoryBitmapAllocationHistoryListView.Items[ GetMemoryBitmapActiveAllocationForStreamIndex( OwnerWindow.CurrentSnapshot.StreamIndex ) ].Selected = true; } else { // refresh panel to clear selection OwnerWindow.MemoryBitmapPanel.Invalidate(); } } private static void ProcessLifecycleForPixel( ulong PointerFromPixel, FCallStack CallStack, FAllocationLifecycle AllocLifecycle, bool bUpdateEndFrame ) { int StartFrame = 1; int EndFrame = 1; bool bAllocated = false; ulong AllocatedPointer = 0; int AllocatedSize = 0; ulong AllocatedStreamIndex = FStreamInfo.INVALID_STREAM_INDEX; if( AllocLifecycle.AllocEvent.Pointer <= PointerFromPixel && AllocLifecycle.AllocEvent.Pointer + ( uint )AllocLifecycle.AllocEvent.Size > PointerFromPixel ) { bAllocated = true; AllocatedPointer = AllocLifecycle.AllocEvent.Pointer; AllocatedSize = AllocLifecycle.AllocEvent.Size; AllocatedStreamIndex = AllocLifecycle.AllocEvent.StreamIndex; StartFrame = FStreamInfo.GlobalInstance.GetFrameNumberFromStreamIndex( EndFrame, AllocLifecycle.AllocEvent.StreamIndex ); } if( AllocLifecycle.ReallocsEvents != null ) { for( int EventIndex = 0; EventIndex < AllocLifecycle.ReallocsEvents.Count; EventIndex++ ) { if( bAllocated ) { if( AllocLifecycle.ReallocsEvents[ EventIndex ].NewPointer > PointerFromPixel || AllocLifecycle.ReallocsEvents[ EventIndex ].NewPointer + ( uint )AllocLifecycle.ReallocsEvents[ EventIndex ].NewSize < PointerFromPixel ) { bAllocated = false; EndFrame = FStreamInfo.GlobalInstance.GetFrameNumberFromStreamIndex( StartFrame, AllocLifecycle.ReallocsEvents[ EventIndex ].StreamIndex ); InsertItemIntoMemoryBitmapListView( CallStack, AllocatedStreamIndex, StartFrame, EndFrame, AllocatedPointer, AllocatedSize ); } } else if( AllocLifecycle.ReallocsEvents[ EventIndex ].NewPointer <= PointerFromPixel && AllocLifecycle.ReallocsEvents[ EventIndex ].NewPointer + ( uint )AllocLifecycle.ReallocsEvents[ EventIndex ].NewSize > PointerFromPixel ) { bAllocated = true; AllocatedPointer = AllocLifecycle.ReallocsEvents[ EventIndex ].NewPointer; AllocatedSize = AllocLifecycle.ReallocsEvents[ EventIndex ].NewSize; AllocatedStreamIndex = AllocLifecycle.ReallocsEvents[ EventIndex ].StreamIndex; StartFrame = FStreamInfo.GlobalInstance.GetFrameNumberFromStreamIndex( EndFrame, AllocLifecycle.ReallocsEvents[ EventIndex ].StreamIndex ); } } } if( bAllocated ) { if( bUpdateEndFrame ) { EndFrame = AllocLifecycle.FreeStreamIndex == FStreamInfo.INVALID_STREAM_INDEX ? -1 : FStreamInfo.GlobalInstance.GetFrameNumberFromStreamIndex( StartFrame, AllocLifecycle.FreeStreamIndex ); } else { EndFrame = 0; } InsertItemIntoMemoryBitmapListView( CallStack, AllocatedStreamIndex, StartFrame, EndFrame, AllocatedPointer, AllocatedSize ); } } public static void BitmapClick( MouseEventArgs e ) { if( MemoryBitmap == null || !FStreamInfo.GlobalInstance.CreationOptions.KeepLifecyclesCheckBox.Checked ) { return; } #if !DEBUG try #endif // !DEBUG { OwnerWindow.SetWaitCursor( true ); UnsafeBitmapClick( e ); OwnerWindow.SetWaitCursor( false ); } #if !DEBUG catch( Exception ex ) { Console.WriteLine( "Problem picking pixel " + ex.Message ); } #endif // !DEBUG } public static void BitmapMouseDown( MouseEventArgs MouseEvents ) { if( OwnerWindow.MemoryBitmapZoomRowsButton.Checked && MouseEvents.Button == MouseButtons.Left && FStreamInfo.GlobalInstance != null ) { ZoomSelectionStartY = MouseEvents.Y; bZoomSelectionInProgress = true; } } public static void BitmapMouseMove( MouseEventArgs MouseEvents ) { if( OwnerWindow.MemoryBitmapZoomRowsButton.Checked && MouseEvents.Button == MouseButtons.Left ) { ZoomSelectionEndY = MouseEvents.Y; OwnerWindow.MemoryBitmapPanel.Invalidate(); } } public static void BitmapMouseUp( MouseEventArgs MouseEvents ) { if( OwnerWindow.MemoryBitmapZoomRowsButton.Checked && MouseEvents.Button == MouseButtons.Left && FStreamInfo.GlobalInstance != null ) { bZoomSelectionInProgress = false; OwnerWindow.MemoryBitmapZoomRowsButton.Checked = false; ZoomSelectionStartY = Math.Max( ZoomSelectionStartY, 0 ); ZoomSelectionEndY = Math.Max( ZoomSelectionEndY, 0 ); int Top = Math.Min( ZoomSelectionStartY, ZoomSelectionEndY ); int Height = Math.Abs( ZoomSelectionEndY - ZoomSelectionStartY ) + 1; // believe it or not, bytesPerLine easily exceeds 2GB in a 64-bit memory space long BytesPerLine = ( long )BytesPerPixel * ( long )MemoryBitmap.Width; Debug.Assert( BytesPerLine > 0 ); ZoomSelectionMemoryBase = FMemoryBitmapParser.GetPointerFromPixel( MemoryBitmap.Width, BytesPerPixel, 0, Top ); ZoomSelectionMemorySize = ( ulong )( Height * BytesPerLine ); bZoomSelectionActive = true; ZoomLevels.Add( new FMemorySpace( ZoomSelectionMemoryBase, ZoomSelectionMemorySize ) ); OwnerWindow.MemoryBitmapUndoZoomButton.Enabled = true; OwnerWindow.MemoryBitmapResetButton.Enabled = true; RefreshMemoryBitmap(); } } public static void UpdateAllocationHistory() { OwnerWindow.MemoryBitmapCallStackListView.BeginUpdate(); OwnerWindow.MemoryBitmapCallStackListView.Items.Clear(); if( OwnerWindow.MemoryBitmapAllocationHistoryListView.SelectedItems.Count > 0 ) { FCallStackTag CallStackTag = ( FCallStackTag )OwnerWindow.MemoryBitmapAllocationHistoryListView.SelectedItems[ 0 ].Tag; FCallStack CallStack = CallStackTag.CallStack; for( int AddressIndex = 0; AddressIndex < CallStack.AddressIndices.Count; AddressIndex++ ) { OwnerWindow.MemoryBitmapCallStackListView.Items.Add( FStreamInfo.GlobalInstance.NameArray[ FStreamInfo.GlobalInstance.CallStackAddressArray[ CallStack.AddressIndices[ AddressIndex ] ].FunctionIndex ] ); } GetPixelRangeFromPointer( MemoryBitmap.Width, MemoryBitmap.Height, BytesPerPixel, CallStackTag.Pointer, CallStackTag.Size ); OwnerWindow.MemoryBitmapPanel.Invalidate(); } if( OwnerWindow.MemoryBitmapAllocationHistoryListView.SelectedItems.Count == 1 ) { FCallStackTag CallStackTag = ( FCallStackTag )OwnerWindow.MemoryBitmapAllocationHistoryListView.SelectedItems[ 0 ].Tag; OwnerWindow.MemoryBitmapCallstackGroupNameLabel.Text = "Group: " + CallStackTag.CallStack.Group.Name; } else { OwnerWindow.MemoryBitmapCallstackGroupNameLabel.Text = "Group: "; } OwnerWindow.MemoryBitmapCallStackListView.EndUpdate(); } private static void InsertItemIntoMemoryBitmapListView( FCallStack CallStack, ulong StreamIndex, int StartFrame, int EndFrame, ulong AllocatedPointer, int AllocatedSize ) { ListViewItem LVItem = new ListViewItem(); LVItem.Text = StartFrame.ToString(); LVItem.SubItems.Add( EndFrame == 0 ? "" : EndFrame.ToString() ); LVItem.SubItems.Add( MainWindow.FormatSizeString( AllocatedSize ) ); LVItem.Tag = new FCallStackTag( CallStack, StartFrame, StreamIndex, AllocatedPointer, ( ulong )AllocatedSize ); bool bInserted = false; for( int ItemIndex = OwnerWindow.MemoryBitmapAllocationHistoryListView.Items.Count - 1; ItemIndex >= 0; ItemIndex-- ) { if( ( ( FCallStackTag )OwnerWindow.MemoryBitmapAllocationHistoryListView.Items[ ItemIndex ].Tag ).StreamIndex <= StreamIndex ) { OwnerWindow.MemoryBitmapAllocationHistoryListView.Items.Insert( ItemIndex + 1, LVItem ); bInserted = true; break; } } if( !bInserted ) { OwnerWindow.MemoryBitmapAllocationHistoryListView.Items.Insert( 0, LVItem ); } } /// /// This function searches the memory bitmap listview for the allocation that was active at the given stream index. /// If there was no active allocation at that index, it returns the previous allocation, or failing that, the first allocation. /// private static int GetMemoryBitmapActiveAllocationForStreamIndex( ulong StreamIndex ) { for( int ItemIndex = OwnerWindow.MemoryBitmapAllocationHistoryListView.Items.Count - 1; ItemIndex >= 0; ItemIndex-- ) { if( ( ( FCallStackTag )OwnerWindow.MemoryBitmapAllocationHistoryListView.Items[ ItemIndex ].Tag ).StreamIndex <= StreamIndex ) { return ItemIndex; } } return 0; } } /// Used as the tag for items in the allocation history list view. public class FCallStackTag { /// Callstack of this allocation. public FCallStack CallStack; /// Position in the stream. public ulong StreamIndex; /// Pointer of allocation. public ulong Pointer; /// Size of allocation. public ulong Size; /// Constructor. public FCallStackTag( FCallStack InCallStack, int InStartFrame, ulong InStreamIndex, ulong InPointer, ulong InSize ) { CallStack = InCallStack; StreamIndex = InStreamIndex; Pointer = InPointer; Size = InSize; } } /// Simple struct used to store current value of zoom level in the memory bitmap view. public struct FMemorySpace { /// Base memory pointer. public ulong Base; /// Memory size. public ulong Size; /// Constructor. public FMemorySpace( ulong InMemoryBase, ulong InMemorySize ) { Base = InMemoryBase; Size = InMemorySize; } } }