Strange performance issues with large bitmap brushes
-
Here's a good one:
HBRUSH hPatternBrush = CreatePatternBrush (hBitmap); // where hBitmap is large
...
OnPaint (HDC hDC)
{
FillRect (hDC, &r, hPatternBrush);
}The FillRect function runs 2-3 times faster (with large bitmaps only) if you use the pattern brush to paint into a memory DC before you do anything else with it. Weird or what? Same behaviour observed on both XP and Vista (without Aero, haven't tried it with), on two PC's with very different graphic cards. Had me going for a while, I can tell you. Hope this helps someone some day.
Paul Sanders http://www.alpinesoft.co.uk
-
Here's a good one:
HBRUSH hPatternBrush = CreatePatternBrush (hBitmap); // where hBitmap is large
...
OnPaint (HDC hDC)
{
FillRect (hDC, &r, hPatternBrush);
}The FillRect function runs 2-3 times faster (with large bitmaps only) if you use the pattern brush to paint into a memory DC before you do anything else with it. Weird or what? Same behaviour observed on both XP and Vista (without Aero, haven't tried it with), on two PC's with very different graphic cards. Had me going for a while, I can tell you. Hope this helps someone some day.
Paul Sanders http://www.alpinesoft.co.uk
The brush is a DDB (device dependent bitmap), I believe. If your original bitmap was a DIB, by converting to a brush early you're eliminating the conversion step from the paint operation.
Software Zen:
delete this;
Fold With Us![^] -
The brush is a DDB (device dependent bitmap), I believe. If your original bitmap was a DIB, by converting to a brush early you're eliminating the conversion step from the paint operation.
Software Zen:
delete this;
Fold With Us![^]Nope, tried that. And a lot of other things, 'cos I couldn't believe what I was seeing. Good idea though, thanks for posting. I'm not sure exactly what goes on behind the scenes when you pass an hBitmap to CreatePatternBrush, but when I passed in a DDB in the 'wrong' format (just to see what would happen) the brush didn't paint properly, so my guess is that the hBitmap is just copied into the brush verbatim. But there is subsequently a step called 'realization' which (I think) happens the first time you select the brush into a DC, so the answer must lie in there somewhere. You would think though that, if anything, selecting a brush into a memory DC before selecting it into a 'real' DC would be more likely to screw things up than the other thing. But evidently not. As a sidenote, I have been testing various bits of slightly sneaky, subclassing code on both XP and Vista and it is uncanny how similarly the two systems behave. Microsoft clearly did not dare to change much, although a few things did break with Aero enabled and I did find a fairly serious bug (theirs, not mine) on Vista 64 bit.
Paul Sanders http://www.alpinesoft.co.uk
-
Nope, tried that. And a lot of other things, 'cos I couldn't believe what I was seeing. Good idea though, thanks for posting. I'm not sure exactly what goes on behind the scenes when you pass an hBitmap to CreatePatternBrush, but when I passed in a DDB in the 'wrong' format (just to see what would happen) the brush didn't paint properly, so my guess is that the hBitmap is just copied into the brush verbatim. But there is subsequently a step called 'realization' which (I think) happens the first time you select the brush into a DC, so the answer must lie in there somewhere. You would think though that, if anything, selecting a brush into a memory DC before selecting it into a 'real' DC would be more likely to screw things up than the other thing. But evidently not. As a sidenote, I have been testing various bits of slightly sneaky, subclassing code on both XP and Vista and it is uncanny how similarly the two systems behave. Microsoft clearly did not dare to change much, although a few things did break with Aero enabled and I did find a fairly serious bug (theirs, not mine) on Vista 64 bit.
Paul Sanders http://www.alpinesoft.co.uk
Paul Sanders (AlpineSoft) wrote:
realization
I bet that's it. That includes a palette conversion step, which could be computationally intensive.
Paul Sanders (AlpineSoft) wrote:
selecting a brush into a memory DC before selecting it into a 'real' DC would be more likely to screw things up
I don't think so. I've got a controls library that uses double-buffering with memory DC's quite a bit, and I've never had an issue with the memory DC behaving differently than drawing directly. My usual practice is to develop the control using direct drawing first, and then switch to double-buffered memory DC drawing when it's done. Of course, now that I'm switching to C# and WPF, I don't have to worry about drawing issues any more :rolleyes: ...
Software Zen:
delete this;
Fold With Us![^] -
Paul Sanders (AlpineSoft) wrote:
realization
I bet that's it. That includes a palette conversion step, which could be computationally intensive.
Paul Sanders (AlpineSoft) wrote:
selecting a brush into a memory DC before selecting it into a 'real' DC would be more likely to screw things up
I don't think so. I've got a controls library that uses double-buffering with memory DC's quite a bit, and I've never had an issue with the memory DC behaving differently than drawing directly. My usual practice is to develop the control using direct drawing first, and then switch to double-buffered memory DC drawing when it's done. Of course, now that I'm switching to C# and WPF, I don't have to worry about drawing issues any more :rolleyes: ...
Software Zen:
delete this;
Fold With Us![^]Gary R. Wheeler wrote:
I don't think so. I've got a controls library that uses double-buffering with memory DC's quite a bit, and I've never had an issue with the memory DC behaving differently than drawing directly. My usual practice is to develop the control using direct drawing first, and then switch to double-buffered memory DC drawing when it's done.
Well, it *behaves* the same sure enough, but drawing performance (FillRect, and PatBlt) is markedly different. It's clear from observed behaviour that the brush 'remembers' something the first time it is selected into a DC. It only kicks in if you use a large patterned brush (the size of the window, in my case, to get a pretty gradient fill) to fill a large area (the window background). What I don't get is what and why. Maybe it is some kind of alignment issue of the bitmap data in memory. Some byte-wise copies go faster when aligned on an 8-byte boundary, but I'm clutching at straws here. Is WPF more programmer-friendly than Win32? I have no experience of it at all. My main complaint about Win32 is the various shortcomings of the common controls. A combination of GDI and GDI+ is pretty effective unless perhaps you want to draw partially see-through objects or 3D gizmos.
Paul Sanders http://www.alpinesoft.co.uk
-
Gary R. Wheeler wrote:
I don't think so. I've got a controls library that uses double-buffering with memory DC's quite a bit, and I've never had an issue with the memory DC behaving differently than drawing directly. My usual practice is to develop the control using direct drawing first, and then switch to double-buffered memory DC drawing when it's done.
Well, it *behaves* the same sure enough, but drawing performance (FillRect, and PatBlt) is markedly different. It's clear from observed behaviour that the brush 'remembers' something the first time it is selected into a DC. It only kicks in if you use a large patterned brush (the size of the window, in my case, to get a pretty gradient fill) to fill a large area (the window background). What I don't get is what and why. Maybe it is some kind of alignment issue of the bitmap data in memory. Some byte-wise copies go faster when aligned on an 8-byte boundary, but I'm clutching at straws here. Is WPF more programmer-friendly than Win32? I have no experience of it at all. My main complaint about Win32 is the various shortcomings of the common controls. A combination of GDI and GDI+ is pretty effective unless perhaps you want to draw partially see-through objects or 3D gizmos.
Paul Sanders http://www.alpinesoft.co.uk
Paul Sanders (AlpineSoft) wrote:
the brush 'remembers' something the first time it is selected into a DC
That sounds likely. Something's getting cached that really effects performance for larger bitmaps. Since "large bitmap brush" support wasn't added until later (Win32), maybe there are some optimizations underneath.
Paul Sanders (AlpineSoft) wrote:
Is WPF more programmer-friendly than Win32? I have no experience of it at all.
I don't know yet; I'm just getting started with it.
Paul Sanders (AlpineSoft) wrote:
A combination of GDI and GDI+ is pretty effective unless perhaps you want to draw partially see-through objects
That's precisely what I was doing on a contracting job last summer. I never did get the drawing speed up to what I wanted; the flicker was obnoxious. If I would have had more time (or the customer more money), I probably could have figured it out, but it was a small job and the performance thing wasn't a big issue for them.
Software Zen:
delete this;
Fold With Us![^] -
Paul Sanders (AlpineSoft) wrote:
the brush 'remembers' something the first time it is selected into a DC
That sounds likely. Something's getting cached that really effects performance for larger bitmaps. Since "large bitmap brush" support wasn't added until later (Win32), maybe there are some optimizations underneath.
Paul Sanders (AlpineSoft) wrote:
Is WPF more programmer-friendly than Win32? I have no experience of it at all.
I don't know yet; I'm just getting started with it.
Paul Sanders (AlpineSoft) wrote:
A combination of GDI and GDI+ is pretty effective unless perhaps you want to draw partially see-through objects
That's precisely what I was doing on a contracting job last summer. I never did get the drawing speed up to what I wanted; the flicker was obnoxious. If I would have had more time (or the customer more money), I probably could have figured it out, but it was a small job and the performance thing wasn't a big issue for them.
Software Zen:
delete this;
Fold With Us![^]Gary R. Wheeler wrote:
Since "large bitmap brush" support wasn't added until later (Win32), maybe there are some optimizations underneath.
Or lack thereof!
Gary R. Wheeler wrote:
I never did get the drawing speed up to what I wanted; the flicker was obnoxious
Yes, GDI / Win32 is sometimes as slow as a one-legged pig doing apparently simple things. I don't think themes help, they seem just kind of stuck on top of everything else. Flicker is most apparent in my app when resizing a window. I have a trick for eliminating it at the cost of some performance. This is probably totally irrelevant to WPF but I guess there's no harm in passing the idea on anyway. What I do is essentially this:
void OnResize (HWND hWnd)
{
SendMessage (hWnd, WM_REDREAW, FALSE, 0);
[resize all the controls in the window; nothing gets redrawn at this point]
ValidateRect (hWnd, NULL);
SendMessage (hWnd, WM_REDREAW, TRUE, 0);
.
[create a memory DC the size of the window]
PrintWindow (hWnd, hMemDC)
hWindowDC = GetDC (hWnd);
BitBlt (hWindowDC, ..., hMemDC, ..., SRCCOPY);
[clean up]}
Nifty, eh? It was a fight to get it to work under Aero - there seem to be a number of subtle bugs in this area - but it was worth it. BTW, whatever happened to the WS_EX_DOUBLEBUFFER flag? There never was one of course, but then it could all be handled transparently (no pun intended) in BeginPaint and EndPaint (and all the standard controls would automatically 'do the right thing'). Just a thought. Email me (or post to this thread) about WPF sometime if you care to. I'd be interested to hear a hardcore WIN32 person's take on it. I'm sure I will take the leap sometime, probably fairly soon. I need to take a look at least.
Paul Sanders http://www.alpinesoft.co.uk
-
Gary R. Wheeler wrote:
Since "large bitmap brush" support wasn't added until later (Win32), maybe there are some optimizations underneath.
Or lack thereof!
Gary R. Wheeler wrote:
I never did get the drawing speed up to what I wanted; the flicker was obnoxious
Yes, GDI / Win32 is sometimes as slow as a one-legged pig doing apparently simple things. I don't think themes help, they seem just kind of stuck on top of everything else. Flicker is most apparent in my app when resizing a window. I have a trick for eliminating it at the cost of some performance. This is probably totally irrelevant to WPF but I guess there's no harm in passing the idea on anyway. What I do is essentially this:
void OnResize (HWND hWnd)
{
SendMessage (hWnd, WM_REDREAW, FALSE, 0);
[resize all the controls in the window; nothing gets redrawn at this point]
ValidateRect (hWnd, NULL);
SendMessage (hWnd, WM_REDREAW, TRUE, 0);
.
[create a memory DC the size of the window]
PrintWindow (hWnd, hMemDC)
hWindowDC = GetDC (hWnd);
BitBlt (hWindowDC, ..., hMemDC, ..., SRCCOPY);
[clean up]}
Nifty, eh? It was a fight to get it to work under Aero - there seem to be a number of subtle bugs in this area - but it was worth it. BTW, whatever happened to the WS_EX_DOUBLEBUFFER flag? There never was one of course, but then it could all be handled transparently (no pun intended) in BeginPaint and EndPaint (and all the standard controls would automatically 'do the right thing'). Just a thought. Email me (or post to this thread) about WPF sometime if you care to. I'd be interested to hear a hardcore WIN32 person's take on it. I'm sure I will take the leap sometime, probably fairly soon. I need to take a look at least.
Paul Sanders http://www.alpinesoft.co.uk
-
Any idea how much hoop jumping would be involved in porting that to a .net app?
Today's lesson is brought to you by the word "niggardly". Remember kids, don't attribute to racism what can be explained by Scandinavian language roots. -- Robert Royall
I'm no .net expert, but on XP, and non-aero Vista, I would think it would be pretty straightforward. The relevant Win32 API calls are readily accessible I believe, albeit via PInvoke, and I assume you can get hold of an HWND to the window you are resizing. That *should* be all you need, but you never know. On an Aero-enabled Vista desktop it is a different story. To get it to work, you have to p*ss around with your window hierarchy. Specifically, you have to place all your controls in a window which is itself a child of the main window. Otherwise, calling PrintWindow causes Vista to redraw the window (with all the associated flicker) even in you call ValidateRect after resizing it. In my case this was not too irksome but in a .net / Windows Forms app I imagine it might be a killer. The other gotcha on Aero is that calling GetDC on a child window and then drawing into that DC does ... nothing! Strange but true. I get around this by getting a DC to the parent window and blting my 'composited' screen into that instead. This seems to work on all platforms (including Vista 64). Another, cleaner solution which I suspect would probably work would be to register your child window class with the CS_OWNDC class style, but I have not tested this as that option was not available to me. I can email you some code snippets if you are serious about this, but if you don't control your window hierarchy it's probably not going to help you much. As a programmer, doncha just *love* Vista? Actually, no! I like C# a lot though; it's what C++ should have been, pretty much. I like your sig by the way. Quite deep, in its way.
Paul Sanders http://www.alpinesoft.co.uk
-
I'm no .net expert, but on XP, and non-aero Vista, I would think it would be pretty straightforward. The relevant Win32 API calls are readily accessible I believe, albeit via PInvoke, and I assume you can get hold of an HWND to the window you are resizing. That *should* be all you need, but you never know. On an Aero-enabled Vista desktop it is a different story. To get it to work, you have to p*ss around with your window hierarchy. Specifically, you have to place all your controls in a window which is itself a child of the main window. Otherwise, calling PrintWindow causes Vista to redraw the window (with all the associated flicker) even in you call ValidateRect after resizing it. In my case this was not too irksome but in a .net / Windows Forms app I imagine it might be a killer. The other gotcha on Aero is that calling GetDC on a child window and then drawing into that DC does ... nothing! Strange but true. I get around this by getting a DC to the parent window and blting my 'composited' screen into that instead. This seems to work on all platforms (including Vista 64). Another, cleaner solution which I suspect would probably work would be to register your child window class with the CS_OWNDC class style, but I have not tested this as that option was not available to me. I can email you some code snippets if you are serious about this, but if you don't control your window hierarchy it's probably not going to help you much. As a programmer, doncha just *love* Vista? Actually, no! I like C# a lot though; it's what C++ should have been, pretty much. I like your sig by the way. Quite deep, in its way.
Paul Sanders http://www.alpinesoft.co.uk
Thanks. The particular customer the app in question is for has spent the last 7 years saying no to XP, so I'm not overly concerned about Vista issues with it yet. I have no idea when/if the app will be getting an update (my fault for delivering a bug free release :doh: ), but am still interested in examples to have on hand whenever the happy day of an update comes.
Today's lesson is brought to you by the word "niggardly". Remember kids, don't attribute to racism what can be explained by Scandinavian language roots. -- Robert Royall
-
Thanks. The particular customer the app in question is for has spent the last 7 years saying no to XP, so I'm not overly concerned about Vista issues with it yet. I have no idea when/if the app will be getting an update (my fault for delivering a bug free release :doh: ), but am still interested in examples to have on hand whenever the happy day of an update comes.
Today's lesson is brought to you by the word "niggardly". Remember kids, don't attribute to racism what can be explained by Scandinavian language roots. -- Robert Royall
OK, here's my collection of horrible hacks. The hWnd parameter is the window you have just resized. hPaintWnd is the window you are going to draw into. On XP, these can be the same (but not under Aero). gIsXP is false on Windows 2000 (where PrintWindow is not supported), true on XP and Vista. The rest you already know. GetRelativeWindowRect is the most useful helper function I ever wrote, by the way. Good luck!
#ifndef PW_CLIENTONLY
#define PW_CLIENTONLY 0x00000001
#endif
.
extern "C"
{
typedef WINUSERAPI BOOL (WINAPI * tPrintWindow) (HWND hwnd, HDC hdcBlt, UINT nFlags);
}
.
// GradientWindow::SmoothUpdate - repaint this window without flicker
void GradientWindow::SmoothUpdate (HWND hWnd, HWND hPaintWnd)
{
if (!IsWindowVisible (hWnd)) // but beware WM_SETREDRAW (TRUE)
return;
.
RECT wr;
wr.left = wr.top = 0;
if (hWnd != hPaintWnd)
GetRelativeWindowRect (hWnd, hPaintWnd, &wr);
hWnd = hPaintWnd;
.
RECT ur;
GetClientRect (hWnd, &ur);
ValidateRect (hWnd, &ur);
.
int width = ur.right;
int height = ur.bottom;
.
RECT r;
GetWindowRect (hWnd, &r);
POINT pt = { 0 };
ScreenToClient (hWnd, &pt);
int x_offset = -pt.x - r.left;
int y_offset = -pt.y - r.top;
width += x_offset;
height += y_offset;
.
for ( ; ; )
{
DWORD style = GetWindowLong (hPaintWnd, GWL_STYLE);
if ((style & WS_CHILD) == 0)
break;
HWND hParent = GetParent (hPaintWnd); // else Aero doesn't draw
if (hParent == NULL)
break;
hPaintWnd = hParent;
}
.
HDC hDC = GetDC (hPaintWnd);
assert (hDC);
wr.left = wr.top = 0;
if (hWnd != hPaintWnd)
GetRelativeWindowRect (hWnd, hPaintWnd, &wr);
.
HDC hMemDC = CreateCompatibleDC (hDC);
assert (hMemDC);
HBITMAP hBitmap = CreateCompatibleBitmap (hDC, width + wr.left, height + wr.top);
assert (hBitmap);
HBITMAP hOldBitmap = (HBITMAP) SelectObject (hMemDC, hBitmap);
.
// We prefer not to use WM_PRINT because:
// (a) it does not paint themed window borders correctly
// (b) URLControl's don't highlight the selected text
// (c) brush origin problems on Windows 2000 (but no gradients there anymore, so OK)
if (!gIsXP) // no PrintWindow () on Windows 2000
SendMessage (hWnd, WM_PRINT, (WPARAM) hMemDC,
PRF_ERASEBKGND | PR