# [.net] GDI+ Horribly Slow. Using DX9 for UserControls good idea?

This topic is 4193 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

## Recommended Posts

Hi there, I need some advice. Working on a control in C#/.NET, I found GDI+ to be horribly slow. I try to implement an excel like grid, with multiselect and all, and when testing with lots of very small cells, you can...watch the thing drawing. I looked for optimizations on my side, found a few things so far and fixed them. Since excel, too, allows to give each cell to have a different color, drawing everything in a block instead of calling the draw function a couple hundred times is no option. Excel does this fast...so why not me. Are there any typical performance pitfalls that I could have stepped in? (All Dr. Google told me is using BackBuffer, and this won't speed up the drawing) Would using DX9 for user controls / apps in general be a good idea? Or... rather not so good? Could it get me into trouble, because it may not run proberly on some clients' systems or something else? Thanks in advance, unshaven

##### Share on other sites
When dealing with the Windows interface, speed comes from not drawing. Draw things once to a buffer, and just bitblit from that buffer each time you need to paint. When things change, just draw the parts that changed to that buffer.

##### Share on other sites
You can also use that strategy in D3D.

##### Share on other sites
You didn't do anything like calling a CreateGraphics function for every line or something?

##### Share on other sites

no, I used the graphics object for one entire frame.
drawing in a buffer-bitmap I already tried, doesn't help.

in the text there are 48x48 small cells.
so > 2000 cell interiors and more grid segments have to be drawn (I do not overdraw anything). (since each thing can have a different color, I have to draw them separately)

there are times where the whole grid/all cells need to be redrawn...
and this takes a notable lot of time...

but in excel this works just fine, even with ridiculously tiny cells.
I guess they won't make a (natively) managed version of excel hehe...

I wonder how excel manages to do this.

I mean.... c'mon... 2400 rectangles + twices as much lines...
An old flat-shaded 3D engine I wrote some years ago could handle more in 3D hehe.
I think I won't try to make a game in managed languages anytime soon.

##### Share on other sites
Don't draw at all like someone said, but skip the buffer. I've found the old GDI to not work that much better/faster after using a buffer. Instead, figure out what areas for each render that needs to be redrawn, and only draw thoughts. In your case, I would imagine a suitable unit for redrawing is one cell. I don't know about Excel versus GDI+ though, it might very well be so that Excel "cheats" and skips a couple of layers in the GDI to gain speed.

##### Share on other sites
Perhaps a snippet of code would be best...

##### Share on other sites
Excel dont need to cheat by skipping layers of the gdi. Windows does have common controls that can be user drawn. Most likly what excel does (though they could be creating there own controls as well. Gdi+ is quite capable at doing what you want it to do.

The problem i bet is that you are fool hardy in your allocations. perhaps you are allocating and deallocating for each cell different brushes and pens without realizing it. Reuse them! brushes have a color proper for a reason.

Post code if you want more detail.

##### Share on other sites
hey, I know the basics of general optimization... ;-)

I use the graphics object for one frame,
and I have stored the pens for each cell & grid segment.

The buffer thing would work when I really do not have to draw the stuff new, but when, say, I want to assign a thicker frame to all of the selected cells (and that be all which are on the screen), I had to redraw ALL borders. And such a thing just can't take a half to one second. That... sucks.
(release mode no difference, btw)

For this and the next week I have no access to the code...

##### Share on other sites
Hi,
I've created some controls in Delphi using the GDI+ too, and they are really slow, and I think I have used it well...

##### Share on other sites
here's some code:

// draws into a bitmap which exists during runtime of the program// this is called once, when the form loads, and each time the form gets resized   // m_Graphics points to the Bitmap m_BackBuffer           		private void repaint_buffer()		{						foreach(Cell c in m_Cells)			{												c.Draw(m_Graphics);			}									m_Grid.Draw(m_Graphics, 0,0 , m_NumCols-1,m_NumRows-1);									m_Graphics.Flush();		}		internal void	UpdateSurface()		{			m_Surface.DrawImage(m_BackBuffer, 0,0);			m_Surface.Flush();		}                // the OnPaint method of the control		private void FTable_Paint(object sender, System.Windows.Forms.PaintEventArgs e)		{			UpdateSurface();						}                // Draw Method of a Cell interior		internal void Draw(Graphics g)		{												Pen backgrpen, forepen;								if (bool_isSelected)				{											backgrpen = m_Pens[0];					forepen =  m_Pens[1];				}				else				{					backgrpen =  m_Pens[2];					forepen =  m_Pens[3];				}														g.FillRectangle(backgrpen.Brush,m_Fillcoords); 				g.DrawString(_Text,m_Font,forepen.Brush,m_Rect,m_StringFormat);				}//end Draw()        // draws the grid lines. since every line segment can have an own color,        // I have to draw the segments separately.	internal void	Draw(Graphics g, int column_start, int row_start, int column_end, int row_end)	{		for (int y=row_start;		y <= 1 + row_end;	++y)		for (int x=column_start;	x < column_end;		++x)		{			LineSeg s = m_HorizSegs[x,y];			g.DrawLine(s.pen, s.x1,s.y1 , s.x2,s.y2);			}		for (int y=row_start;		y <= row_end;			++y)		for (int x=column_start;	x < 1 + column_end;		++x)		{			LineSeg s = m_VertSegs[x,y];			g.DrawLine(s.pen, s.x1,s.y1 , s.x2,s.y2);			}	}

So, resizing is horribly slow, takes 1/4s or so,
and moving the window with one side across the screen border, you still notice some delay (jumpiness of movement), although there's done nothing more than drawing one shitty bitmap! (which has the size of the control)

##### Share on other sites
I don't know much about user controls, but I've seen these tips. Use these settings so only your control handles painting:

Setstyle(Controlstyles.DoubleBuffer, true);
Setstyle(Controlstyles.Selectable, true);
Setstyle(Controlstyles.UserPaint, true);
Setstyle(Controlstyles.AllPaintingInWmPaint, true);

Also, you can only redraw cells in the dirty region that needs repainting. I forget how to do that offhand. Also I assume of course you are only drawing visible cells. If you do keep an offscreen bitmap/picture, it only needs to be as big as the control.

You'll get better help over in the Windows Forms forums:
http://www.windowsforms.net/Forums/ShowForum.aspx?tabIndex=1&tabId=41&ForumID=7

##### Share on other sites
the settings dont change a thing.
thanks for the link, I'll look there later.

and here are some numbers:

Quote:
 MyControls Number of Called Methods: 200 Percent of Time Spent in Image: 2,0 TestCtrl Number of Called Methods: 32 Percent of Time Spent in Image: 0,5

--> The application itself doesnt use so much cycles
Most of the time is spent in GDI+ and kernel32.dll, 58.1% in the function Sleep()
WTF ??? Never called this function in the entire program, so, where does it get called? Somewhere in the GDI, when it can't yet return from a fuction, because it has to finish its drawing or somethin like that??

Quote:
 System and Other DLLs kernel32.dll Number of Called Methods: 150 Percent of Time Spent in Image: 59,3 GdiPlus.dll Number of Called Methods: 68 Percent of Time Spent in Image: 19,6 System.Drawing Number of Called Methods: 316 Percent of Time Spent in Image: 6,2 mscorlib Number of Called Methods: 943 Percent of Time Spent in Image: 4,3 System.Windows.Forms Number of Called Methods: 1087 Percent of Time Spent in Image: 3,4 ntdll.dll Number of Called Methods: 10 Percent of Time Spent in Image: 2,6 user32.dll Number of Called Methods: 101 Percent of Time Spent in Image: 0,9

##### Share on other sites
I would suggest two things. First, replace the foreach statement with a for loop, it's supposedly as much as 40% slower. Second, throw all your painting into a thread, it might help to increase the APPARENT speed of your application.

##### Share on other sites
hehe, altho the loop overhead doesnt matter here,

just for the interest, I read several articles about the differences in performance between foreach, for loop using .Length every iteration, and storing length temporarily.

A lot of people who are considered experts said it won't really matter,
or showed that the generated IL listing would be sometimes a few instructions longer with for, and blah, others saying the JIT can't optimize if you use temporary...

I did a quick test (edit: corrected copy&paste symptom ;) ), looping through a 100MB int array, running the code with all options a few times to make sure the JIT optimization had taken place.

here's the code
using System;namespace ConsoleApplication1{	class Class1	{		[STAThread]		static void Main(string[] args)		{			if (args.Length < 1)			{				display_options();			 return;			}					int[] arr = new int[25 * 1024*1024];	// 4*25 MB = 100MB			int tickct_start, tickct_end, ticks_diff;							switch (args[0])			{				case "-fe":	{tickct_start=System.Environment.TickCount; test__foreach(arr);}		break;				case "-fl":	{tickct_start=System.Environment.TickCount; test__for_length(arr);}	break;				case "-ft":	{tickct_start=System.Environment.TickCount; test__for_temp(arr);}	break;								default:	display_options();				return;			}						tickct_end = System.Environment.TickCount;			ticks_diff = tickct_end - tickct_start;						Console.WriteLine();			Console.WriteLine("time elapsed: " + ticks_diff);			Console.WriteLine();		}				static void	display_options()		{					Console.WriteLine();			Console.WriteLine("Options:");			Console.WriteLine("-fe : foreach");			Console.WriteLine("-fl : for with .length");			Console.WriteLine("-ft : for with temporary len");			Console.WriteLine();		}						static void	test__foreach(int[] arr)		{						foreach (int i in arr);				}				static void	test__for_length(int[] arr)		{			int dummy;						for (int i=0;	i<arr.Length;	++i)				dummy = arr;		}		static void	test__for_temp(int[] arr)		{			int dummy;						for (int i=0,len=arr.Length;	i<len;	++i)				dummy = arr;		}			}}

results on an AMD Athlon 1GHz, 512MB RAM,
Windows XP,
after running each option several times for "warmup":
(I ran the exe from the cmd command prompt, outside devenv)

(time in "ticks")

debug:
--------
foreach: 490..510
for .length: 461..471
for temp: 390..401

release:
--------
foreach: 390..411
for .length: 380..520
for temp: 420..431

While for seems a little quicker in debug build, with temp it seems a little slower using release build, so the optimization thing that was mentioned may be the reason.
This is compiled with VS 2003, don't know if it would be different with 2005.

[Edited by - UnshavenBastard on September 25, 2006 7:57:28 AM]

##### Share on other sites
Did you get anywhere with this ?

I've got the same problem drawing a custom grid of cells with styling.
I've even tried using the BufferedGraphics objects, but rendering a screen full of cells with text and formatting takes nearly a second a pop.

Did you get anywhere with using directX ?

Matt

##### Share on other sites
I find the trick with GDI+ is cache often, draw less-often, and chop things up into controls where you can. Oh and don't forget to dipose of your drawing objects when you are done. Also perception is king; if an app hangs when drawing it *appears* to the user to take forever, whereas handling drawing on a seperate thread, drawing a "loading" image, then drawing the result on the UI thread *appears* to take less time - when infact it would probabbly take longer ;)

##### Share on other sites
I implemented an excel clone the other day with gdi+, and have not had a great deal of difficulty with redrawing cells.

Setstyle to optimized double buffer.

Don't react to events to do the redrawing of cells if this is what you are doing.
Handle that redrawing without it being fired from an event.
Basically avoid handling much of anything in events, but keep them only as hooks for the end user to gain access to your code.

If you are looping through your list of items then break after the visible items are done being viewed.
Start looping at the first visible item.

Use clipping when appropriate. (For example: Instead of wrapping text, or using a loop to find where to cut off text in a cell.)

As a side note I use a dirived List<> class to allow events for add/remove/whatever of items.

Pipe all of your invalidate calls through a single method that can output a message during debugging. Use the System.Diagnostics.Debug.WriteLine routine to show your message to the output window, this will help you find where you are unnecessarily calling invalidate() on your control.

Surround everything that you can (escpecially loops) in if statements to cut down on unnecessary processing. Check null, empty, bounds, etc.

Avoid using excess memory in your control. This will add pressure to the garbage collector later.

Avoid weak references.
Hook and unhook events with += and -= when appropriate

Loops or hittests are probably the culprit, but finally, use a profiler if you still can't find the bottleneck.