/** * Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; using System.Diagnostics; using System.Windows.Forms; using System.ComponentModel; using System.Drawing.Drawing2D; namespace MemoryProfiler2 { public static class FCallStackHistoryView { private const int CALLSTACK_HISTORY_GRAPH_X_BORDER = 30; private const int CALLSTACK_HISTORY_GRAPH_Y_BORDER = 40; private const int MAX_CALLSTACK_HISTORY_ZOOM_HORIZONTAL = 16; private const int MAX_CALLSTACK_HISTORY_ZOOM_VERTICAL = 16; public static List HistoryCallStacks = new List(); private static List GraphPoints = new List(); private static Bitmap GraphBitmap; /// True if the graph generator was aborted. private static bool bIsIncomplete; private static Rectangle GraphRect = new Rectangle( 90, 30, 0, 0 ); private static double YAxisHeight = 1024; public static int VZoom = 1; public static int HZoom = 1; private static int StartSnapshotIndex; private static int EndSnapshotIndex; private static Font SnapshotFont = new Font( FontFamily.GenericSansSerif, 13, GraphicsUnit.Pixel ); private static Pen GrayPen = new Pen( Color.FromArgb( 0xd0, 0xd0, 0xd0 ) ); private static Pen DarkGrayPen = new Pen( Color.FromArgb( 0x80, 0x80, 0x80 ) ); private static Pen GrayDashedPen = new Pen( Color.FromArgb( 0xd0, 0xd0, 0xd0 ) ); /// Reference to the main memory profiler window. private static MainWindow OwnerWindow; public static void SetProfilerWindow( MainWindow InMainWindow ) { GrayDashedPen.DashStyle = DashStyle.Dash; OwnerWindow = InMainWindow; } public static void SetHistoryCallstacks( List CallStacks ) { OwnerWindow.UpdateStatus( "Updating history graph for " + OwnerWindow.CurrentFilename ); HistoryCallStacks = CallStacks; if( HistoryCallStacks.Count > 0 ) { long MaxSize; CombineSizeGraphsAsync( HistoryCallStacks, GraphPoints, out MaxSize ); YAxisHeight = Math.Max( MaxSize, 1024 ); } HZoom = 1; VZoom = 1; RefreshCallStackHistoryGraph(); UpdateCallstackHistoryZoomLabel(); } private static void CombineSizeGraphsAsync( List InCallStacks, List CombinedGraphPoints, out long MaximumSize ) { long TempMaximumSize = 0; OwnerWindow.ProgressDialog.OnBeginBackgroundWork = delegate( BackgroundWorker BGWorker, DoWorkEventArgs EventArgs ) { CombineSizeGraphs( InCallStacks, CombinedGraphPoints, BGWorker, out TempMaximumSize, EventArgs ); bIsIncomplete = false; }; if( OwnerWindow.ProgressDialog.ShowDialog() != DialogResult.OK ) { bIsIncomplete = true; OwnerWindow.UpdateStatus( "Failed to create history graph, due to '" + OwnerWindow.ProgressDialog.ExceptionResult + "'" ); } else { OwnerWindow.UpdateStatus( "Displaying " + OwnerWindow.CurrentFilename ); } MaximumSize = TempMaximumSize; OwnerWindow.CallStackHistoryHScroll.Visible = true; OwnerWindow.CallStackHistoryVScroll.Visible = true; } private static void CombineSizeGraphs( List InCallStacks, List CombinedGraphPoints, BackgroundWorker BGWorker, out long MaximumSize, DoWorkEventArgs EventArgs ) { BGWorker.ReportProgress( 0, "Combining size graphs" ); MaximumSize = 0; CombinedGraphPoints.Clear(); int TotalGraphPoints = 0; List CallStacks = new List( InCallStacks.Count ); // Cache this to avoid the hit of calling ToUpperInvariant for every callstack string FilterText = OwnerWindow.FilterTextBox.Text.ToUpperInvariant(); foreach( FCallStack CallStack in InCallStacks ) { // leave out callstacks with empty sizegraphs or not in the selected memory pool if( CallStack.SizeGraphPoints.Count > 0 && CallStack.RunFilters( FilterText, OwnerWindow.Options.ClassGroups, OwnerWindow.IsFilteringIn(), OwnerWindow.SelectedMemoryPool ) ) { TotalGraphPoints += CallStack.SizeGraphPoints.Count; CallStacks.Add( CallStack ); } } List Indices = new List( CallStacks.Count ); for( int i = 0; i < CallStacks.Count; i++ ) { Indices.Add( 0 ); } int ProgressUpdateCounter = 0; long Size = 0; while( CallStacks.Count > 0 ) { // Check for pending cancellation of a background operation. if( BGWorker.CancellationPending ) { EventArgs.Cancel = true; return; } ulong MinStreamIndex = ulong.MaxValue; int BestGraphIndex = -1; bool bBestPointIsFreshRealloc = false; for( int Index = 0; Index < Indices.Count; Index++ ) { FSizeGraphPoint SizeGraphPoint = CallStacks[ Index ].SizeGraphPoints[ Indices[ Index ] ]; ulong StreamIndex = SizeGraphPoint.StreamIndex; if( StreamIndex < MinStreamIndex ) { MinStreamIndex = StreamIndex; BestGraphIndex = Index; bBestPointIsFreshRealloc = SizeGraphPoint.bFreshRealloc; } else if( StreamIndex == MinStreamIndex ) { if( bBestPointIsFreshRealloc && !SizeGraphPoint.bFreshRealloc ) { // this point beats the current best point, because the best point is a fresh realloc // (which causes less accurate memory accounting) and this point isn't bBestPointIsFreshRealloc = SizeGraphPoint.bFreshRealloc; // skip previous best point Indices[ BestGraphIndex ]++; if( Indices[ BestGraphIndex ] >= CallStacks[ BestGraphIndex ].SizeGraphPoints.Count ) { // point was last in graph, so remove callstack CallStacks.RemoveAt( BestGraphIndex ); Indices.RemoveAt( BestGraphIndex ); // bestGraph must be less than i, so we need to adjust i after removing bestGraph Index--; } BestGraphIndex = Index; } else { // This point is worse than or the same as the best point, so skip it to stop it // from coming up in the next iteration (we don't want two graph points for the same // streamindex). Indices[ Index ]++; if( Indices[ Index ] >= CallStacks[ Index ].SizeGraphPoints.Count ) { // point was last in graph, so remove callstack and continue processing from next callstack CallStacks.RemoveAt( Index ); Indices.RemoveAt( Index ); Index--; } } } } // make sure no two graph points have the same streamindex Debug.Assert( CombinedGraphPoints.Count == 0 || CombinedGraphPoints[ CombinedGraphPoints.Count - 1 ].StreamIndex != MinStreamIndex ); FSizeGraphPoint BestPoint = CallStacks[ BestGraphIndex ].SizeGraphPoints[ Indices[ BestGraphIndex ] ]; Size += BestPoint.SizeChange; CombinedGraphPoints.Add( new FSizeGraphPoint( MinStreamIndex, BestPoint.SizeChange, false ) ); Indices[ BestGraphIndex ]++; if( Indices[ BestGraphIndex ] >= CallStacks[ BestGraphIndex ].SizeGraphPoints.Count ) { CallStacks.RemoveAt( BestGraphIndex ); Indices.RemoveAt( BestGraphIndex ); } if( Size > MaximumSize ) { MaximumSize = Size; } ProgressUpdateCounter++; if( BGWorker != null && ProgressUpdateCounter > 10000 ) { BGWorker.ReportProgress( ( int )( ( float )CombinedGraphPoints.Count / ( float )TotalGraphPoints * 100 ), "Combining size graphs" ); ProgressUpdateCounter = 0; } } } public static void ClearView() { GraphPoints.Clear(); OwnerWindow.CallStackHistoryHScroll.Visible = false; OwnerWindow.CallStackHistoryVScroll.Visible = false; } public static void ResetZoom() { HZoom = 1; VZoom = 1; OwnerWindow.CallstackHistoryResetZoomButton.Enabled = false; RefreshCallStackHistoryGraph(); UpdateCallstackHistoryZoomLabel(); } public static void UpdateZoom( int Delta, ref int ZoomValue ) { int OldZoomValue = ZoomValue; ZoomValue += Delta; if( ZoomValue < 1 ) { ZoomValue = 1; } else if( ZoomValue > 10 ) { ZoomValue = 10; } if( ZoomValue > 1 ) { OwnerWindow.CallstackHistoryResetZoomButton.Enabled = true; } if( HZoom == 1 && VZoom == 1 ) { OwnerWindow.CallstackHistoryResetZoomButton.Enabled = false; } if( ZoomValue != OldZoomValue ) { RefreshCallStackHistoryGraph(); UpdateCallstackHistoryZoomLabel(); } } private static void UpdateCallstackHistoryZoomLabel() { OwnerWindow.CallstackHistoryZoomLabelH.Text = HZoom.ToString(); OwnerWindow.CallstackHistoryZoomLabelV.Text = VZoom.ToString(); } public static void RefreshCallStackHistoryGraph() { if( OwnerWindow == null ) { return; } GraphRect.Width = OwnerWindow.CallStackHistoryPanel.Width - GraphRect.X - OwnerWindow.CallStackHistoryVScroll.Width - CALLSTACK_HISTORY_GRAPH_X_BORDER; GraphRect.Height = OwnerWindow.CallStackHistoryPanel.Height - GraphRect.Y - OwnerWindow.CallStackHistoryHScroll.Height - CALLSTACK_HISTORY_GRAPH_Y_BORDER; if( FStreamInfo.GlobalInstance == null || GraphPoints.Count == 0 ) { // force panel to be redrawn blank so that any old data is cleared OwnerWindow.CallStackHistoryPanel.Invalidate(); return; } if( GraphRect.Width < 1 || GraphRect.Height < 1 ) { // window is probably minimised, or at least too small to display a graph return; } OwnerWindow.SetWaitCursor( true ); int GraphWidth = ( int )( HZoom * GraphRect.Width ); int GraphHeight = ( int )( VZoom * GraphRect.Height ); Size OldGraphSize = new Size(); if( GraphBitmap != null ) { OldGraphSize = GraphBitmap.Size; GraphBitmap.Dispose(); GraphBitmap = null; } while( true ) { try { GraphBitmap = new Bitmap( GraphWidth, GraphHeight ); break; } catch( Exception ) { // sometimes this fails with an ArgumentException when the arguments are fine // i suspect it means to throw OutOfMemoryException, as it only occurs with large images if( GraphBitmap != null && ( GraphWidth > OldGraphSize.Width || GraphHeight > OldGraphSize.Height ) ) { // try to use the old size instead GraphWidth = OldGraphSize.Width; GraphHeight = OldGraphSize.Height; } else { // abort the whole function return; } } } SetUpScrollBar( OwnerWindow.CallStackHistoryHScroll, GraphWidth, GraphRect.Width ); SetUpScrollBar( OwnerWindow.CallStackHistoryVScroll, GraphHeight, GraphRect.Height ); if( OwnerWindow.CallstackHistoryShowCompleteHistoryButton.Checked ) { StartSnapshotIndex = -1; EndSnapshotIndex = FStreamInfo.GlobalInstance.SnapshotList.Count - 1; } else { StartSnapshotIndex = OwnerWindow.GetStartSnapshotIndex(); EndSnapshotIndex = OwnerWindow.GetEndSnapshotIndex(); // swap snapshots if end is before start if( EndSnapshotIndex < StartSnapshotIndex ) { int Temp = StartSnapshotIndex; StartSnapshotIndex = EndSnapshotIndex; EndSnapshotIndex = Temp; } } float XBase; float XScale; if( OwnerWindow.CallstackHistoryAxisUnitFramesButton.Checked ) { int StartFrame = StartSnapshotIndex == -1 ? 0 : FStreamInfo.GlobalInstance.SnapshotList[ StartSnapshotIndex ].FrameNumber; int EndFrame = EndSnapshotIndex == -1 ? 0 : FStreamInfo.GlobalInstance.SnapshotList[ EndSnapshotIndex ].FrameNumber; int FrameRange = EndFrame - StartFrame; XScale = ( float )GraphWidth / ( float )FrameRange; XBase = StartFrame * XScale; } else { ulong StartStreamIndex = StartSnapshotIndex == -1 ? 0 : FStreamInfo.GlobalInstance.SnapshotList[ StartSnapshotIndex ].StreamIndex; ulong EndStreamIndex = EndSnapshotIndex == -1 ? 0 : FStreamInfo.GlobalInstance.SnapshotList[ EndSnapshotIndex ].StreamIndex; ulong StreamIndexRange = EndStreamIndex - StartStreamIndex; XScale = ( float )( ( double )GraphWidth / ( double )StreamIndexRange ); XBase = StartStreamIndex * XScale; } float YScale = ( float )( GraphHeight / YAxisHeight ); RenderSizeGraph( Graphics.FromImage( GraphBitmap ), GraphPoints, GraphWidth, GraphHeight, XBase, XScale, YScale, OwnerWindow.CallstackHistoryAxisUnitFramesButton.Checked, !bIsIncomplete ); OwnerWindow.CallStackHistoryPanel.Invalidate(); OwnerWindow.SetWaitCursor( false ); } private static void RenderSizeGraph( Graphics MyGraphics, List GraphPoints, int GraphWidth, int GraphHeight, float XBase, float XScale, float YScale, bool bXAxisIsFrames, bool bShouldCompleteGraph ) { // Progress bar. OwnerWindow.ToolStripProgressBar.Value = 0; OwnerWindow.ToolStripProgressBar.Visible = true; long ProgressInterval = GraphPoints.Count / 20; long NextProgressUpdate = ProgressInterval; Debug.Assert( GraphPoints != null && GraphPoints.Count > 0 ); //MyGraphics.SmoothingMode = SmoothingMode.AntiAlias; FSizeGraphPoint OldPoint = new FSizeGraphPoint( GraphPoints[ 0 ].StreamIndex, 0, false ); float OldX = 0; float OldY = 0; int NextFrame = 1; int CurrentSize = 0; for( int PointIndex = 0; PointIndex < GraphPoints.Count; PointIndex++ ) { // Update progress bar. if( PointIndex >= NextProgressUpdate ) { OwnerWindow.ToolStripProgressBar.PerformStep(); NextProgressUpdate += ProgressInterval; Debug.WriteLine( "FCallStackHistoryView.RenderSizeGraph " + OwnerWindow.ToolStripProgressBar.Value + "/20" ); } FSizeGraphPoint SizeGraphPoint = GraphPoints[ PointIndex ]; float X; if( bXAxisIsFrames ) { X = CalculateXForTime( ref NextFrame, SizeGraphPoint.StreamIndex ) * XScale - XBase; } else { X = SizeGraphPoint.StreamIndex * XScale - XBase; } CurrentSize += SizeGraphPoint.SizeChange; float Y = CurrentSize * YScale; if( X >= 0 ) { if( X < GraphWidth ) { MyGraphics.DrawLine( Pens.Black, OldX, GraphHeight - OldY, X, GraphHeight - OldY ); MyGraphics.DrawLine( Pens.Black, X, GraphHeight - OldY, X, GraphHeight - Y ); } else { break; } } OldPoint = SizeGraphPoint; OldX = X; OldY = Y; } // Connect final point to right edge of graph. if( bShouldCompleteGraph ) { float FinalLineY = GraphHeight - OldY; MyGraphics.DrawLine( Pens.Black, OldX, FinalLineY, GraphWidth, FinalLineY ); } OwnerWindow.ToolStripProgressBar.Visible = false; } private static void SetUpScrollBar( ScrollBar MyScrollBar, int Maximum, int WindowWidth ) { MyScrollBar.Maximum = Maximum; MyScrollBar.LargeChange = WindowWidth + 1; // The maximum value reachable by the user with the scrollbar (different to Maximum). int MaximumScrollable = Maximum - WindowWidth; int Value = MyScrollBar.Value; if( Value > MaximumScrollable ) { Value = MaximumScrollable; } if( Value < 0 ) { Value = 0; } MyScrollBar.Value = Value; } private static float CalculateXForTime( ref int MinFrame, ulong StreamIndex ) { MinFrame = FStreamInfo.GlobalInstance.GetFrameNumberFromStreamIndex( MinFrame, StreamIndex ); ulong PrevStreamIndex = FStreamInfo.GlobalInstance.FrameStreamIndices[ MinFrame - 1 ]; ulong StreamIndicesInFrame = FStreamInfo.GlobalInstance.FrameStreamIndices[ MinFrame ] - PrevStreamIndex; return MinFrame - 1 + ( float )( StreamIndex - PrevStreamIndex ) / ( float )StreamIndicesInFrame; } public static void PaintHistoryPanel( PaintEventArgs e ) { if( FStreamInfo.GlobalInstance == null || GraphPoints.Count == 0 ) { e.Graphics.DrawString( "No data to display", SnapshotFont, Pens.Black.Brush, GraphRect.Left, GraphRect.Top ); } else { PaintCallStackHistoryGraph( e.Graphics, SnapshotFont, GraphRect, ( float )YAxisHeight, OwnerWindow.CallStackHistoryHScroll.Value, OwnerWindow.CallStackHistoryVScroll.Value, HZoom, VZoom, OwnerWindow.CallstackHistoryAxisUnitFramesButton.Checked, OwnerWindow.CallstackHistoryShowSnapshotsButton.Checked, GraphBitmap, bIsIncomplete ); } } private static void PaintCallStackHistoryGraph( Graphics MyGraphics, Font SnapshotFont, Rectangle GraphRect, float YAxisHeight, int HScrollValue, int VScrollValue, int HZoom, int VZoom, bool bXAxisIsTime, bool bShowSnapshots, Bitmap GraphImage, bool bGraphIncomplete ) { int GraphTop = GraphRect.Top; int GraphHeight = GraphRect.Height; int GraphLeft = GraphRect.Left; int GraphWidth = GraphRect.Width; string YAxisLabel; long MajorTick; MainWindow.SetUpAxisLabel( VZoom, ref YAxisHeight, out YAxisLabel, out MajorTick ); // Make sure that majorTick is divisible by four, so that minorTick divides into it without remainder. MajorTick = MajorTick / 4 * 4; long MinorTick = MajorTick / 4; // Y axis MyGraphics.DrawLine( Pens.Black, GraphLeft, GraphTop, GraphLeft, GraphTop + GraphHeight ); int TrueGraphHeight = ( int )( VZoom * GraphHeight ); OwnerWindow.DrawYAxis( MyGraphics, Pens.Black, MajorTick, MainWindow.DEFAULT_MAJOR_TICK_WIDTH, MinorTick, MainWindow.DEFAULT_MINOR_TICK_WIDTH, GraphLeft, GraphTop, GraphHeight, VScrollValue, TrueGraphHeight, YAxisLabel, YAxisHeight, false, false ); // X axis MyGraphics.DrawLine( Pens.Black, GraphLeft, GraphTop + GraphHeight, GraphLeft + GraphWidth, GraphTop + GraphHeight ); // Shade loadmaps and GCs. Brush LoadMapBrush = new SolidBrush( Color.FromArgb( 209, 223, 255 ) ); Brush GCBrush = new SolidBrush( Color.FromArgb( 205, 255, 204 ) ); int StartSnapshot = Math.Max( StartSnapshotIndex, 0 ); int EndSnapshot = EndSnapshotIndex; ulong StartStreamIndex = StartSnapshotIndex == -1 ? 0 : FStreamInfo.GlobalInstance.SnapshotList[ StartSnapshotIndex ].StreamIndex; ulong EndStreamIndex = EndSnapshotIndex == -1 ? 0 : FStreamInfo.GlobalInstance.SnapshotList[ EndSnapshotIndex ].StreamIndex; int StartFrame = StartSnapshotIndex == -1 ? 0 : FStreamInfo.GlobalInstance.SnapshotList[ StartSnapshotIndex ].FrameNumber; int EndFrame = EndSnapshotIndex == -1 ? 0 : FStreamInfo.GlobalInstance.SnapshotList[ EndSnapshotIndex ].FrameNumber; float XScale; if( bXAxisIsTime ) { int FrameRange = EndFrame - StartFrame; XScale = ( float )GraphWidth / ( float )FrameRange * HZoom; } else { ulong StreamIndexRange = ( ulong )( EndStreamIndex - StartStreamIndex ); XScale = ( float )GraphWidth / ( float )StreamIndexRange * HZoom; } // Draw frame ticks. if( bXAxisIsTime ) { int FrameTick = Math.Max( ( int )( 4 / XScale ), 1 ); for( int FrameIndex = 0; FrameIndex < FStreamInfo.GlobalInstance.FrameStreamIndices.Count; FrameIndex += FrameTick ) { float FrameX = FrameIndex * XScale; if( FrameX >= HScrollValue && FrameX <= HScrollValue + GraphWidth ) { float panelX = GraphLeft + FrameX - HScrollValue; if( bXAxisIsTime ) { // Draw vertical dashed line. MyGraphics.DrawLine( GrayDashedPen, panelX, GraphTop, panelX, GraphTop + GraphHeight ); } } } MyGraphics.DrawString( "Frames per gridline: " + FrameTick, SnapshotFont, Pens.Black.Brush, GraphLeft, GraphTop + GraphHeight + 5 ); } int MinFrame = 1; if( bShowSnapshots ) { if( FStreamInfo.GlobalInstance.CreationOptions.LoadMapStartSnapshotsCheckBox.Checked && FStreamInfo.GlobalInstance.CreationOptions.LoadMapEndSnapshotsCheckBox.Checked ) { ulong LoadMapStartStreamIndex = 0; bool bInLoadmap = false; for( int SnapshotIndex = StartSnapshot; SnapshotIndex <= EndSnapshot; SnapshotIndex++ ) { if( FStreamInfo.GlobalInstance.SnapshotList[ SnapshotIndex ].SubType == EProfilingPayloadSubType.SUBTYPE_SnapshotMarker_LoadMap_Start ) { LoadMapStartStreamIndex = FStreamInfo.GlobalInstance.SnapshotList[ SnapshotIndex ].StreamIndex; bInLoadmap = true; } else if( FStreamInfo.GlobalInstance.SnapshotList[ SnapshotIndex ].SubType == EProfilingPayloadSubType.SUBTYPE_SnapshotMarker_LoadMap_End || ( SnapshotIndex == FStreamInfo.GlobalInstance.SnapshotList.Count - 1 && bInLoadmap ) ) { float VirtualStartX, VirtualEndX; if( bXAxisIsTime ) { VirtualStartX = CalculateXForTime( ref MinFrame, LoadMapStartStreamIndex ) - StartFrame; VirtualEndX = CalculateXForTime( ref MinFrame, FStreamInfo.GlobalInstance.SnapshotList[ SnapshotIndex ].StreamIndex ) - StartFrame; } else { VirtualStartX = LoadMapStartStreamIndex - StartStreamIndex; VirtualEndX = FStreamInfo.GlobalInstance.SnapshotList[ SnapshotIndex ].StreamIndex - StartStreamIndex; } float StartX = Math.Min( Math.Max( GraphLeft + VirtualStartX * XScale - HScrollValue, GraphLeft ), GraphLeft + GraphWidth ); float EndX = Math.Min( Math.Max( GraphLeft + VirtualEndX * XScale - HScrollValue, GraphLeft ), GraphLeft + GraphWidth ); if( StartX != EndX ) { MyGraphics.FillRectangle( LoadMapBrush, StartX, GraphTop, EndX - StartX, GraphHeight ); } bInLoadmap = false; } } } // Shade GC regions on top of LoadMap regions. if( FStreamInfo.GlobalInstance.CreationOptions.GCStartSnapshotsCheckBox.Checked && FStreamInfo.GlobalInstance.CreationOptions.GCEndSnapshotsCheckBox.Checked ) { ulong GCStartStreamIndex = 0; bool bInGC = false; MinFrame = 1; for( int SnapshotIndex = 0; SnapshotIndex < FStreamInfo.GlobalInstance.SnapshotList.Count; SnapshotIndex++ ) { if( FStreamInfo.GlobalInstance.SnapshotList[ SnapshotIndex ].SubType == EProfilingPayloadSubType.SUBTYPE_SnapshotMarker_GC_Start ) { GCStartStreamIndex = FStreamInfo.GlobalInstance.SnapshotList[ SnapshotIndex ].StreamIndex; bInGC = true; } else if( FStreamInfo.GlobalInstance.SnapshotList[ SnapshotIndex ].SubType == EProfilingPayloadSubType.SUBTYPE_SnapshotMarker_GC_End || ( SnapshotIndex == FStreamInfo.GlobalInstance.SnapshotList.Count - 1 && bInGC ) ) { float VirtualStartX, VirtualEndX; if( bXAxisIsTime ) { VirtualStartX = CalculateXForTime( ref MinFrame, GCStartStreamIndex ) - StartFrame; VirtualEndX = CalculateXForTime( ref MinFrame, FStreamInfo.GlobalInstance.SnapshotList[ SnapshotIndex ].StreamIndex ) - StartFrame; } else { VirtualStartX = GCStartStreamIndex - StartStreamIndex; VirtualEndX = FStreamInfo.GlobalInstance.SnapshotList[ SnapshotIndex ].StreamIndex - StartStreamIndex; } float StartX = Math.Min( Math.Max( GraphLeft + VirtualStartX * XScale - HScrollValue, GraphLeft ), GraphLeft + GraphWidth ); float EndX = Math.Min( Math.Max( GraphLeft + VirtualEndX * XScale - HScrollValue, GraphLeft ), GraphLeft + GraphWidth ); if( StartX != EndX ) { MyGraphics.FillRectangle( GCBrush, StartX, GraphTop, EndX - StartX, GraphHeight ); } bInGC = false; } } } LoadMapBrush.Dispose(); GCBrush.Dispose(); // Draw snapshot lines. MinFrame = 1; for( int SnapshotIndex = 0; SnapshotIndex < FStreamInfo.GlobalInstance.SnapshotList.Count; SnapshotIndex++ ) { float SnapshotX; if( bXAxisIsTime ) { SnapshotX = ( CalculateXForTime( ref MinFrame, FStreamInfo.GlobalInstance.SnapshotList[ SnapshotIndex ].StreamIndex ) - StartFrame ) * XScale; } else { SnapshotX = ( FStreamInfo.GlobalInstance.SnapshotList[ SnapshotIndex ].StreamIndex - StartStreamIndex ) * XScale; } if( SnapshotX >= HScrollValue && SnapshotX <= HScrollValue + GraphWidth ) { float panelX = GraphLeft + SnapshotX - HScrollValue; MyGraphics.DrawLine( GrayPen, panelX, GraphTop, panelX, GraphTop + GraphHeight - 1 ); } } } // Copy region from graph image. try { MyGraphics.DrawImage( GraphImage, GraphRect, HScrollValue, VScrollValue, GraphRect.Width, GraphRect.Height, GraphicsUnit.Pixel ); } catch( Exception e ) { if( e is OutOfMemoryException || e is ArgumentException ) { MyGraphics.DrawString( "NOTE: Graph failed to render: Try zooming out", SnapshotFont, Brushes.Red, GraphLeft, 0 ); } else { throw e; } } if( bShowSnapshots ) { // Label snapshots. float SnapshotRight = -1; float LastTextY = GraphTop; MinFrame = 1; for( int SnapshotIndex = 0; SnapshotIndex < FStreamInfo.GlobalInstance.SnapshotList.Count; SnapshotIndex++ ) { float SnapshotX; if( bXAxisIsTime ) { SnapshotX = ( CalculateXForTime( ref MinFrame, FStreamInfo.GlobalInstance.SnapshotList[ SnapshotIndex ].StreamIndex ) - StartFrame ) * XScale; } else { SnapshotX = ( FStreamInfo.GlobalInstance.SnapshotList[ SnapshotIndex ].StreamIndex - StartStreamIndex ) * XScale; } if( SnapshotX >= HScrollValue && SnapshotX <= HScrollValue + GraphWidth ) { float TextY = GraphTop; if( SnapshotX < SnapshotRight ) { TextY = LastTextY + SnapshotFont.Height; } LastTextY = TextY; float SnapshotTextWidth = MyGraphics.MeasureString( FStreamInfo.GlobalInstance.SnapshotList[ SnapshotIndex ].Description, SnapshotFont ).Width; if( SnapshotX + SnapshotTextWidth > SnapshotRight ) { SnapshotRight = SnapshotX + SnapshotTextWidth; } float PanelX = GraphLeft + SnapshotX - HScrollValue; MyGraphics.DrawString( FStreamInfo.GlobalInstance.SnapshotList[ SnapshotIndex ].Description, SnapshotFont, Brushes.White, PanelX - 1, TextY - 1 ); MyGraphics.DrawString( FStreamInfo.GlobalInstance.SnapshotList[ SnapshotIndex ].Description, SnapshotFont, Brushes.White, PanelX - 1, TextY + 1 ); MyGraphics.DrawString( FStreamInfo.GlobalInstance.SnapshotList[ SnapshotIndex ].Description, SnapshotFont, Brushes.White, PanelX + 1, TextY - 1 ); MyGraphics.DrawString( FStreamInfo.GlobalInstance.SnapshotList[ SnapshotIndex ].Description, SnapshotFont, Brushes.White, PanelX + 1, TextY + 1 ); MyGraphics.DrawString( FStreamInfo.GlobalInstance.SnapshotList[ SnapshotIndex ].Description, SnapshotFont, DarkGrayPen.Brush, PanelX, TextY ); } } } if( bGraphIncomplete ) { MyGraphics.DrawString( "NOTE: This graph is incomplete", SnapshotFont, Brushes.Red, GraphLeft, 0 ); } } } }