Bitblt Question

Jun 19, 2012 at 2:46pm
Hi, I'm having problems with screen flicker and as I understand it BitBlting is the way to go.

In all of the examples I see, however, they use bitmaps.... I'm not using any bitmaps, just a lot of DrawText() calls.

Can BitBlt be used for DrawTexts?

I imagine that it would look something like this?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
PAINTSTRUCT ps;
HDC hdc,compat;
RECT rect;
...
...
case WM_PAINT:
	GetClientRect(MsghWnd,&rect);
	BeginPaint(MsghWnd,&ps);
	hdc=GetDC(MsghWnd);
	compat=CreateCompatibleDC(hdc);
	DrawText(compat,"test text",strlen("test text"),&rect,DT_TOP|DT_LEFT);
	BitBlt(hdc,0,0,rect.right,rect.bottom,compat,0,0,SRCCOPY);
	DeleteDC(compat);
	EndPaint(MsghWnd,&ps);


Or, since I'm only drawing text, is there a different approach that may work better?

One problem I was having was that when I dragged the window, the text would redraw all over itself - the text would get super thick and black. It was still legible, but it definitely didn't look right.

To resolve that problem, I simply invalidated the window in WM_MOVE to force it to repaint. This resolved that problem perfectly... however it created the screen flashing problem. There were other occurrences of the screen flashing, but this was by far the most dramatic case of it.

The program is laid out in a MDI-ish approach. Each client window is fixed in place, but it is a parent window with a number of client windows... the parent window never has anything to draw, it's all the child windows. And each child window has an object associated with it. Would I possibly be able to reduce the flashing problem by adding an HDC to each object, letting it keep track of when the text has changed, and simply BitBlting the stored DC rather than re-DrawText-ing everything each paint?
Last edited on Jun 19, 2012 at 2:47pm
Jun 19, 2012 at 2:57pm
Not just "blitting" bitmaps. The whole technique is called "double buffering". Google it up.

Basic steps:

1. Obtain the HDC via a call to BeginPaint() and NEVER any other way while processing WM_PAINT.
2. Create a compatible DC.
3. Create a compatible bitmap of the correct size.
4. Select the compatible bitmap inside the compatible DC. Save the old bitmap for later.
5. Draw over the compatible DC.
6. After all drawing is complete on the compatible DC, blit the result back into the HDC provided by BeginPaint().
7. Clean up. It means call EndPaint(), select old bitmap into compatible DC, delete bitmap, delete compatible DC, delete all other GDI objects if you aren't reusing them.
Jun 19, 2012 at 3:41pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	PAINTSTRUCT ps;
	HDC hdc,compatDC;
	RECT rect;
	HBITMAP compatBitmap;
	HANDLE oldBitmap;

case WM_PAINT:
	hdc=BeginPaint(MsghWnd,&ps);
	GetClientRect(MsghWnd,&rect);
	compatDC=CreateCompatibleDC(hdc);
	compatBitmap=CreateCompatibleBitmap(compatDC,rect.right,rect.bottom);
	oldBitmap=SelectObject(compatDC,compatBitmap);
	monitors[i]->Paint(compatDC);  // this is a series of calls to DrawText with object-specific info in it
	BitBlt(hdc,0,0,rect.right,rect.bottom,compatDC,0,0,SRCCOPY);
	SelectObject(compatDC,oldBitmap);
	DeleteObject(compatBitmap);
	DeleteDC(compatDC);
	EndPaint(MsghWnd,&ps);
break;


I think I'm close... the following code will generate a bunch of black windows. A quick google search says that the next step is to FillRect with the desired colour.

The desired colour alternates, to make differentiating different windows visually easier.

The following produces a white box, instead.... which is not the correct color in question... my text still not showing up.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
	PAINTSTRUCT ps;
	HDC hdc,compatDC;
	RECT rect;
	HBITMAP compatBitmap;
	HANDLE oldBitmap;

case WM_PAINT:
	hdc=BeginPaint(MsghWnd,&ps);
	GetClientRect(MsghWnd,&rect);
	compatDC=CreateCompatibleDC(hdc);
	compatBitmap=CreateCompatibleBitmap(compatDC,rect.right,rect.bottom);
	oldBitmap=SelectObject(compatDC,compatBitmap);
	FillRect(compatDC,&rect,CreateSolidBrush(GetBkColor(compatDC))); /// THIS IS THE NEW LINE
	monitors[i]->Paint(compatDC);
	BitBlt(hdc,0,0,rect.right,rect.bottom,compatDC,0,0,SRCCOPY);
	SelectObject(compatDC,oldBitmap);
	DeleteObject(compatBitmap);
	DeleteDC(compatDC);
	EndPaint(MsghWnd,&ps);
break;


My theory (or query to you?) as to why it's still not showing, is that somehow the rect is on top of the text?
Last edited on Jun 19, 2012 at 3:42pm
Jun 19, 2012 at 4:08pm
Your double buffering looks OK now.

You say you are getting a box of a color that is not expected. What color are you expecting? After all, you ARE using GetBkColor() for the compatible DC and you haven't even specified a back color for this DC. Just because it is COMPATIBLE with some other DC doesn't mean it is a CLONE of it or that some of the properties are cloned.

Also, if you create a brush you must delete it. Using CreateSolidBrush() like that is leaking the GDI object. Eventually your program will stop drawing if you don't correct this leak.

And one more thing: Don't provide a background brush for the window that is being doubly buffered. It will cause flickering. Do background erasing in the compatible bitmap only.
Last edited on Jun 19, 2012 at 4:10pm
Jun 19, 2012 at 4:19pm
Well, I have 3 WNDCLASSEX objects... one for the parent window (just a vanilla window, nothing fancy).

The other two WNDCLASSEX objects are for the child windows... within WinMain:
1
2
3
4
5
6
7
8
9
//initialize all of the other member vars of wcex1
......
wcex1.hbrBackground=CreateSolidBrush(RBG(245,245,245));
.....

//initialize all of the other member vars of wcex2
.....
wcex2.hbrBackground=CreateSolidBrush(RBG(240,245,245)
.....


I've changed my WM_PAINT to (I hope?) remove the GDI leak:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
HBRUSH hbrush;

case WM_PAINT:
	hdc=BeginPaint(MsghWnd,&ps);
	GetClientRect(MsghWnd,&rect);
	compatDC=CreateCompatibleDC(hdc);
	compatBitmap=CreateCompatibleBitmap(compatDC,rect.right,rect.bottom);
	oldBitmap=SelectObject(compatDC,compatBitmap);
	hbrush=CreateSolidBrush(GetBkColor(hdc));    //altered line, changed DC being queried for BkColor
	FillRect(compatDC,&rect,hbrush);               //altered line, using new hbrush
	DeleteObject(hbrush);                       //altered line, deleting brush when finished with it
	monitors[i]->Paint(compatDC);
	BitBlt(hdc,0,0,rect.right,rect.bottom,compatDC,0,0,SRCCOPY);
	SelectObject(compatDC,oldBitmap);
	DeleteObject(compatBitmap);
	DeleteDC(compatDC);
	EndPaint(MsghWnd,&ps);

break;
Last edited on Jun 19, 2012 at 4:19pm
Jun 19, 2012 at 4:32pm
Yes, leak removed.

What about those background brushes? They look good as long as they aren't for doubly-buffered windows. If they are doubly-buffered, remove them. Instead create those brushes on demand while drawing in-memory.
Jun 19, 2012 at 5:19pm
Hmm... the following created an unexpected result - the background now is white, but covered in little black dots (textured, if you will).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
HBRUSH hbrush;

case WM_PAINT:
	hdc=BeginPaint(MsghWnd,&ps);
	GetClientRect(MsghWnd,&rect);
	compatDC=CreateCompatibleDC(hdc);
	compatBitmap=CreateCompatibleBitmap(compatDC,rect.right,rect.bottom);
	oldBitmap=SelectObject(compatDC,compatBitmap);
	if (i%2==0) /// "i" exists elsewhere in the code, in a for loop - this code reduced for simplicity.
		hbrush=CreateSolidBrush(RGB(245,245,245));
	else
		hbrush=CreateSolidBrush(RGB(240,245,245));
	FillRect(compatDC,&rect,hbrush);
	DeleteObject(hbrush);
	monitors[i]->Paint(compatDC);
	BitBlt(hdc,0,0,rect.right,rect.bottom,compatDC,0,0,SRCCOPY);
	SelectObject(compatDC,oldBitmap);
	DeleteObject(compatBitmap);
	DeleteDC(compatDC);
	EndPaint(MsghWnd,&ps);

break;
Last edited on Jun 19, 2012 at 5:20pm
Jun 19, 2012 at 7:10pm
There's nothing in what you should that would hint where the problem is. Debug consciously to find the offending line or lines. I simply cannot tell from that.
Jun 19, 2012 at 7:13pm
Darn. Okay, firing up the debugger! I'll post back when I've either into the problem, the solution, or another question.

Thanks for your other help, it is greatly appreciated.
Jun 19, 2012 at 8:23pm
Got it!

From MSDN, on the topic of CreateCompatibleDC():
A memory DC exists only in memory. When the memory DC is created, its display surface is exactly one monochrome pixel wide and one monochrome pixel high.

From MSDN, on the topic of CreateCompatibleBitmap():
Because memory device contexts allow both color and monochrome bitmaps, the format of the bitmap returned by the CreateCompatibleBitmap function differs when the specified device context is a memory device context. However, a compatible bitmap that was created for a nonmemory device context always possesses the same color format and uses the same color palette as the specified device context.

So, to resolve my problem, I had to change this line:
 
compatBitmap=CreateCompatibleBitmap(compatDC,rect.right,rect.bottom);


To this:
 
compatBitmap=CreateCompatibleBitmap(hdc,rect.right,rect.bottom);


My child windows are now in the correct color, with text showing up correctly.

This did not, however, resolve my screen flicker. Do you know of another way that I could attempt to resolve this?
Jun 19, 2012 at 8:25pm
Did you remove the brush from the window classes?
Jun 19, 2012 at 8:30pm
As of my previous post, I did not remove them - I simply declared two global HBRUSH objects, brush1 and brush2, assigned values in WinMain(), and them used them as needed.

DestroyObject() is called after the WinMain message loop for the two brushes.

I did just observe an issue that could be causing it though... will post back when I've identified/fixed that.
Jun 19, 2012 at 8:36pm
Well, like I said earlier: Don't allow Windows to erase the background because it will flicker. In order actually enforce this you can take one of two routes:

1. DON'T provide background brushes when registering the window class.
2. Process WM_ERASEBKGND (or similar name, don't recall right now) and do NOTHING.

If the window class specifies a background brush and you pass WM_ERASEBKGND to the default window procedure, the background is going to be erased. Don't let this happen.
Jun 19, 2012 at 8:52pm
Beautiful. Absolutely beautiful.

The flicker is gone!

Now... that "other" issue I mentioned... I've identified -what- it is... still working on where it's coming from and why. If I should start a new question, let me know - sounds like I'm diverging away from the Double Buffering/BitBlting that the subject of the thread is about.

My message loop is getting flooded with SPI_SETSCREENSAVETIMEOUT messages... according to my debug, the UINT message within my WndProc is constantly showing a value of 15.

Looking up the value of 0x000F in WinUser.h is where I find the SPI_SETSCREENSAVETIMEOUT message name.

It's being flooded so badly that WM_TIMER isn't even being processed.
Jun 19, 2012 at 9:21pm
Start a new question, but FYI: SPI_SETSCREENSAVETIMEOUT is NOT a message. It is merely a constant that happens to be of value 15. WM_PAINT is 0x00F. You are not really being flooded by those, you just happen to never stop them coming if you are attempting to debug your WM_PAINT code. WM_PAINT will loop if you try to debug it via breakpoints.
Jun 19, 2012 at 9:30pm
To test this, I have implemented the following code, using a global int tempcounter, within my WndProc:

1
2
3
4
5
	if (message==15){
		char t[50];
		OutputDebugString(itoa(tempcounter++,t,10));
		OutputDebugString("\r\n");
	}


With all breakpoints removed, the number being output increases by roughly 800 per second.

I'll open a new question. Thank you again for your help!!
Topic archived. No new replies allowed.