GDI+ backbuffer?

Started by
13 comments, last by Aardvajk 15 years, 6 months ago
I'm trying to draw an image with semitransparent pixels that updates every 25 ticks. If I don't clear the background, the semitransparent pixels build up and make opaque pixels. If I do clear the background, it flickers every time the mouse moves, so I'm trying to write it to a different DC and blit it to achieve a backbuffer effect. The problem is, GDI+'s Graphics class doesn't seem to like writing to the other DC, and it doesn't copy anything. When I blit one rect of the screen to the other, it works fine, so its not a problem with the blit function. Here's my paint code:
hDC = GetDC(hWnd);
dDC = CreateCompatibleDC(hDC);
Graphics Gr(dDC);
Gdiplus::Rect topRect(0,0,1024,24);
Gr->SetClip(topRect,CombineModeReplace);
Gr->Clear(Gdiplus::Color::White);
Gr->DrawImage(topbar,0,0,128,24);
BitBlt(hDC,0,0,1024,768,dDC,0,0,SRCCOPY);

Do you have any other suggestions? I tried writing to a blank Image and that didn't do it either. Does GDI+ just not work with CompatibleDC's?
It's a sofa! It's a camel! No! It's Super Llama!
Advertisement
In normal Win32, a DC is not a surface to draw on so much as an interface to another surface. So your screen DC is an interface to the screen, a DC with a bitmap selected into it is an interface to the bitmap, you can get a DC for a printer, a Direct3D surface etc. It unifies all the GDI functions so they can draw to different types of destination.

In normal GDI, you would also create a compatible bitmap and select it into the compatible DC to create an offscreen surface.

Rather than get into the details of that, I'd rather see the code for where you tried to create a GDI+ image, draw to that then draw that to your visible window since there is no reason that wouldn't work and would be far, far simpler than messing about with old GDI offscreen surface code.

I'll go knock up a GDI+ based function that does that and post it here.
I basically had:

Gdiplus::Bitmap OffScreen(1024, 768, 32);
Graphics Gr(&OffScreen);
Gdiplus::Rect topRect(0,0,1024,24);
Gr->SetClip(topRect,CombineModeReplace);
Gr->Clear(Gdiplus::Color::White);
Gr->DrawImage(topbar,0,0,128,24);
Graphics GrS(hDC);
GrS->DrawImage(&OffScreen,0,0,1024,768);
It's a sofa! It's a camel! No! It's Super Llama!
Hmm. That in itself should work, but I'm not sure you are letting Windows update your window.

The following program uses a similar approach to fill an offscreen surface with a random colour then copy it to the screen:

#include <windows.h>#include <string>#include <sstream>#include <cstdlib>#include <gdiplus.h>using namespace Gdiplus;#pragma comment(lib,"gdiplus.lib")namespace{LRESULT CALLBACK WndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam);int Register(HINSTANCE HIn);}int WINAPI WinMain(HINSTANCE HIn,HINSTANCE,LPSTR CmdLine,int Show){    HWND Hw=NULL;    MSG Msg;    ULONG_PTR gdiplusToken;    if(!Register(HIn)) return 0;    Hw=CreateWindowEx(0,"Win","Driver",WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN,(1280-300)/2,(1024-420)/2,300,420,GetDesktopWindow(),NULL,HIn,NULL);    if(Hw==NULL) return 0;    GdiplusStartupInput gdiplusStartupInput;    GdiplusStartup(&gdiplusToken,&gdiplusStartupInput,NULL);        ShowWindow(Hw,Show); UpdateWindow(Hw); SetFocus(Hw);    PeekMessage(&Msg,NULL,0,0,PM_NOREMOVE);    while(Msg.message!=WM_QUIT)        {        if(PeekMessage(&Msg,NULL,0,0,PM_REMOVE))            {            TranslateMessage(&Msg);            DispatchMessage(&Msg);            }                    InvalidateRect(Hw,NULL,false);        }    GdiplusShutdown(gdiplusToken);    return static_cast<int>(Msg.wParam);}namespace{void OnPaint(HDC Dc){    Graphics Gr(Dc);    Bitmap B(100,100);    int r=std::rand()%256;    int g=std::rand()%256;    int b=std::rand()%256;    Graphics BGr(&B);    BGr.FillRectangle(&(SolidBrush(Color(r,g,b))),0,0,100,100);    Gr.DrawImage(&B,0,0,100,100);}LRESULT CALLBACK WndProc(HWND Hw,UINT Msg,WPARAM wParam,LPARAM lParam){    PAINTSTRUCT Ps;    HDC Dc;    switch(Msg)        {        case WM_DESTROY: PostQuitMessage(0); break;        case WM_PAINT  : Dc=BeginPaint(Hw,&Ps); OnPaint(Dc); EndPaint(Hw,&Ps); break;        default: return DefWindowProc(Hw,Msg,wParam,lParam);        }            return 0;}int Register(HINSTANCE HIn){    WNDCLASSEX Wc;    Wc.cbSize=sizeof(WNDCLASSEX);    Wc.style=0;    Wc.lpfnWndProc=WndProc;    Wc.cbClsExtra=0;    Wc.cbWndExtra=0;    Wc.hInstance=HIn;    Wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);    Wc.hCursor=LoadCursor(NULL,IDC_ARROW);    Wc.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);    Wc.lpszMenuName=NULL;    Wc.lpszClassName="Win";    Wc.hIconSm=LoadIcon(NULL,IDI_APPLICATION);    return RegisterClassEx(&Wc);}}


Notice I am calling InvalidateRect() every loop. This generates a WM_PAINT message than calls my Paint handler, and my update is done within this handler.

I think you need to maintain your offscreen surface somewhere and draw it to the screen from within the WM_PAINT handler. After you update the offscreen surface, call InvalidateRect() as in the above program to generate a WM_PAINT.

Sorry if this explanation isn't too clear but hopefully the code above will help.

[EDIT] Actually, I'll try to explain a bit, then everyone else can pick my explanation to bits [smile].

In Windows, a window doesn't have its contents saved anywhere offscreen by default. There's a good reason for this - say you have, for some odd reason, a 1000x1000 pixel window whose contents are always solid black. There is no point reserving 1000x1000x4 bytes of system memory to store this window in addition to its space in the screen buffer - if at any point the window needs to be updated, you can far more efficiently just refill the screen buffer pixels with black.

If you want to store the contents of your window in memory as well as on the screen, you have to do this manually by programatically creating your own offscreen buffer of the relevant size, drawing to it and copying your offscreen buffer to the screen buffer at the appropriate times.

Obviously a screen buffer is maintained in graphics memory of the current screen as a whole, but individual windows are only redrawn when Windows considers all or a part of them to be invalid.

Several things can invalidate all or part of a window. When an application is first shown, the window is invalid. Minimising then maximising a window will invalidate the window. Moving another window over the window then away again will invalidate the underlying window. These invalidations are triggered by activities in the OS, outside of your program.

You can tell Windows that your window is invalid within your program by calling the InvalidateRect() method above.

When a window is invalid, Windows sends the application a WM_PAINT message. This message is basically telling the application that the window on the screen needs to be redrawn.

All of this obviously prevents the need for all windows to the screen to be constantly redrawing themselves every time the screen refreshes.

If you have chosen to implement an offscreen buffer for your window, you can draw to this offscreen buffer whenever you want. However, the only time you should or need to copy this buffer to the actual window on the screen is when the screen window is invalid, so you do it in response to a WM_PAINT.

In my example code, because I want the window to be constantly updating, I elect to tell Windows that the window is invalid every time my message loop executes, by calling InvalidateRect().

In this example, I'm creating and drawing to the offscreen buffer within the WM_PAINT handler. A more normal application might maintain the screen buffer outside of this method, update to it when another action causes it to be invalid, then tell Windows that something has happened to change the display - i.e. invalidate the window and generate a WM_PAINT message. In this case, all the WM_PAINT handler need do is copy the offscreen to the screen window.

Is that at all clear? Bit rambly.

[Edited by - EasilyConfused on October 9, 2008 3:40:50 PM]
Great, that looks like it should work. I had been ignoring WM_PAINT and directly drawing with the hDC, and I completely forgot about InvalidateRect().

I'm implimenting it now :)
It's a sofa! It's a camel! No! It's Super Llama!
Okay it works quite well, but if I put the Bitmap outside of the WM_PAINT block, it has an error. If I declare it inside the WM_PAINT, everything works fine, but then it can only be used by that function. I could probably make this work for me, but do you have any idea why its doing this?

Thanks for the loads of help you've already given me [smile]

EDIT:

Uh oh, now its painting one frame and making the same error...
It's a sofa! It's a camel! No! It's Super Llama!
You're welcome.

Post code. Pointless guessing. [smile]
Paint block:
case WM_PAINT:		{			PAINTSTRUCT lPS;			BeginPaint(hWnd,&lPS);			Gdiplus::Bitmap ScreenBuff(1024,768);			hDC = lPS.hdc;			Graphics Gr(hDC);			Graphics OffS(&ScreenBuff);			OffS.Clear(Gdiplus::Color::White);			OffS.DrawImage(iToDraw,0,0,128,24);			Gr.DrawImage(&ScreenBuff,0,0,1024,768);			EndPaint(hWnd,&lPS);		}


The error tells me that the OffS variable has a null NativeGraphics variable. Not sure why this happens on the second frame and not the first...
It's a sofa! It's a camel! No! It's Super Llama!
Your code looks functionally equivalent to mine, and I've just left mine running for five minutes and used Task Manager to check there were no GDI leaks.

My guess is that perhaps, since you are not returning zero at the end of your WM_PAINT handler, perhaps you are falling through to DefWindowProc() and that is causing some obscure problem.

You should return zero if you handle WM_PAINT yourself (put return 0; after your EndPaint line) but I can't tell if that is causing the problem you describe.

If that doesn't work, is your whole program of a size that you could post it in entirety in [ source ][ /source ] (no spaces) tags?
ah, yes, that fixed it. Thank you :D

Now here's another wierd problem: every time I move the mouse, the image stops moving...
It's a sofa! It's a camel! No! It's Super Llama!

This topic is closed to new replies.

Advertisement