Sign in to follow this  
Super Llama

GDI+ backbuffer?

Recommended Posts

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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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);

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
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...

Share this post


Link to post
Share on other sites
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...

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
Yay [smile].

Re it stopping moving, I'd guess your message loop is perhaps different to mine and you are only calling your update-and-invalidate method in the absence of other windows messages.

When you are moving a mouse across a window, it is getting a constant stream of WM_MOUSEMOVE messages so unless you are calling your update function after every message, you won't get WM_PAINTs in between them.

Post your message pump and show how you are calling your update function "every 25 ticks".

Share this post


Link to post
Share on other sites

MSG msg;
while(true)
{
DWORD startpoint = GetTickCount();
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
while ((GetTickCount() - startpoint) < 25);
if (Quitting==false)
InvalidateRect(hWnd,NULL,false);
}
return msg.wParam;

Share this post


Link to post
Share on other sites
Hmm. You don't really want to delay your message pump while you wait for a tick. I think that WM_MOUSEMOVE messages are getting queued up in that time maybe. Not too sure.

I'd do something like this untested version:


DWORD startpoint=GetTickCount();

while(true)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if(msg.message == WM_QUIT) break;
// I assume you set your Quitting variable in your WM_CLOSE handler or something

TranslateMessage(&msg);
DispatchMessage(&msg);
}

if (Quitting==false)
{
DWORD point=GetTickCount();

if(point-startpoint>=25)
{
InvalidateRect(hWnd,NULL,false);
startpoint=point;
}
}
}

return msg.wParam;


Basically, every loop, process a message if there is one. Call your update when the tickcount passes the threshold, but don't stop your main loop waiting for it.

Share this post


Link to post
Share on other sites
Yay! That's probably what's wrong.

Hmmmmm now it only updates when the window doesn't have focus... very strange...

EDIT: Actually I found out that it never gets to the InvalidateRect() function, so it only updates when the window is painted by windows.

EDIT AGAIN:

Okay its fixed, I misinterpreted your example and accidentally put the GetTickCount reset function INSIDE the loop, which caused both variables to be the same the whole time.


It works Perfectly now, thank you so much for all the help [wink]

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this