Thread: Double buffering in GDI -- easy?

  1. #1
    Registered User
    Join Date
    Jan 2003
    Posts
    35

    Double buffering in GDI -- easy?

    The GDI game I'm working on is finally within a few parsecs of something distantly related to completion. But before I move on to the next stage I want to ask a few questions. I've seen some good GDI games on this board and so I'd like to tap into your expertise.

    First off, is double buffering really easy in the GDI, e.g. just a question of drawing to a compatible memory dc and then blitting that dc to the window's dc? I made a small stab at it and I got nothing but a black screen.

    Also, does double buffering solve the problem of "tearing"? That's the only reason I'd want to use it. If I blit too fast the bitmaps have slices taken out, and I've had to use convoluted methods of drawing only now and again to solve the problem. It's particularly irksome with text. I have too many timers going on to control what gets drawn when. I'd like to be able to draw text and everything every frame and have it come out smooth & solid.

    If someone could outline the steps for GDI DB I'd much appreciate it.

  2. #2
    Registered User
    Join Date
    Apr 2002
    Posts
    1,571
    Double Buffering will solve your problem and bake you a cake also. Its not that complicated at all.

    Creation:
    1) Create a compatible DC
    2) Create a compatible Bitmap ( sizeof screen )
    3) Select the bitmap into the comp. DC

    Drawing:
    1) Clear the back buffer BitBlt with WHITENESS or BLACKNESS
    2) Draw everything to the back buffer
    3) Blt the back buffer to the main DC

    Deletion:
    1) Delete the compatible DC
    2) Delete the compatible Bitmap

    Should get the job done.

  3. #3
    Registered User
    Join Date
    Jan 2003
    Posts
    35
    Cool, thanks man.

  4. #4
    Registered User
    Join Date
    Jan 2003
    Posts
    35
    I put off posting this as long as I could, figuring I'd try out every possibility, but, well, here I am.

    I've started to implement double buffering, and it works like a charm except that Windows has some kind of problem with what I'm doing.

    When I move my mouse outside of my program's window, it gets all slow the way it does when Windows is mad because I'm not calling BeginPaint/EndPaint.

    I am calling begin/end paint. No painting is happening but that's always the case anyway. When I draw directly to the front DC, the problem goes away and Windows is happy.

    I can't figure out why this is happening and it's driving me nuts. Internally (that is, while the mouse cursor is over the program's window) everything runs just fine. There are no compiler complaints and no shutdown problems or anything, just the slow cursor which means that something's not right with the way my program is running.

    Basically I've done what I thought I was meant to do:

    Code:
    void Bitmaps::Init(HWND hw )
    {
       init=true;
       hwnd = hw;
       hdc = GetDC(hwnd );
       hdcBuf = CreateCompatibleDC(hdc );
       hbmBackBM = CreateCompatibleBitmap(hdc, 640, 480 );
       hbmOldBackBM = (HBITMAP)SelectObject(hdcBuf, hbmBackBM );
       BitBlt(hdcBuf, 0, 0, 640, 480, NULL, 0, 0, BLACKNESS );
       BitBlt(hdc, 0, 0, 640, 480, NULL, 0, 0, BLACKNESS );
    }
    This is from the initialisation routine from the bitmaps class. Once created, everything is drawn to the back buffer and then I call SwapBuffers:

    Code:
    void Bitmaps::SwapBuffers()
    {
       BitBlt(hdc, 0, 0, 640, 480, hdcBuf, 0, 0, SRCCOPY);
       BitBlt(hdcBuf, 0, 0, 640, 480, NULL, 0, 0, BLACKNESS );
    }
    I'm pretty sure the problem isn't in this code, but I hardly know where else to look since everything else is the same except I'm drawing to the back DC instead of the front one. Any hints?

  5. #5
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    In Mr Wizard's post he forgot to mention that GDI resources must be carefully monitored.

    >>MrWizard
    >>1) Delete the compatible DC
    >>2) Delete the compatible Bitmap

    Not correct.

    A GDI object currently selected into a DC can not be deleted.
    So you must first select the original bitmap (or GDI resource) back into the DC,
    delete the created GDI resource,
    delete the DC.

    Unless you retain the return from a SelectObject() to return it on close you will create a memory leak.


    fusikon
    Code:
    void Bitmaps::Init(HWND hw )
    {
       init=true;
       hwnd = hw;
       hdc = GetDC(hwnd );
       hdcBuf = CreateCompatibleDC(hdc );
       hbmBackBM = CreateCompatibleBitmap(hdc, 640, 480 );
       hbmOldBackBM = (HBITMAP)SelectObject(hdcBuf, hbmBackBM );
       BitBlt(hdcBuf, 0, 0, 640, 480, NULL, 0, 0, BLACKNESS );
       BitBlt(hdc, 0, 0, 640, 480, NULL, 0, 0, BLACKNESS );
    //release the GetDC() resource
    ReleaseDC(hwnd,hdc);
    
    }
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  6. #6
    Registered User
    Join Date
    Jan 2003
    Posts
    35
    Okay, I understand about the deselecting and releasing of the DCs, which I do at the end of my program. The GetDC() resource is the front DC I'm blitting into, so I want to keep that throughout the program. The Bitmap class's destructor releases the DCs when it goes out of scope.

  7. #7
    Registered User
    Join Date
    Apr 2002
    Posts
    1,571
    Originally posted by novacain
    In Mr Wizard's post he forgot to mention that GDI resources must be carefully monitored.

    >>MrWizard
    >>1) Delete the compatible DC
    >>2) Delete the compatible Bitmap

    Not correct.

    A GDI object currently selected into a DC can not be deleted.
    So you must first select the original bitmap (or GDI resource) back into the DC,
    delete the created GDI resource,
    delete the DC.
    Argh! Thats what I get for being in a hurry when typing stuff out. Thanks for pointing that out.

  8. #8
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    >>The GetDC() resource is the front DC I'm blitting into, so I want to keep that throughout the program.

    IMHO I would not do that.

    How do know that your hdc from GetDC() is not the same as the one returned / in BeginPaint() as they have the same HWND?
    So you could be copying from the same DC to itself.

    I would create two compatible DC's, both based on your hdc from GetDC().

    One is drawn on to when anything changes on screen.

    The second is a copy of the first. When the drawing is finished, the first is bitblt'ed to the second.

    As the app gets msg's to update teh screen ( WM_PAINT ect) the second DC is bitblt'ed to the DC returned from / in the call to BeginPaint().

    Before BeginPaint() validates the update region I use GetUpdateRect() to reduce the area that must be drawn (say if a small area has been covered up by a messagebox ect)




    The other thing to look at is the style WS_CLIPCHILDREN
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  9. #9
    Registered User
    Join Date
    Jan 2003
    Posts
    35
    Let's pull this apart here, because it's not making much sense to me.

    Here's the Init function I posted. This function is called at the start of the program to set up the DCs used by every other function in the program:

    Code:
    void Bitmaps::Init(HWND hw )
    {
       init=true;
       hwnd = hw;
       hdc = GetDC(hwnd );
       hdcBuf = CreateCompatibleDC(hdc );
       hbmBackBM = CreateCompatibleBitmap(hdc, 640, 480 );
       hbmOldBackBM = (HBITMAP)SelectObject(hdcBuf, hbmBackBM );
       BitBlt(hdcBuf, 0, 0, 640, 480, NULL, 0, 0, BLACKNESS );
       BitBlt(hdc, 0, 0, 640, 480, NULL, 0, 0, BLACKNESS );
    }
    There is no DC returned from BeginPaint. Or at least, I'm not assigning it to anything. BeginPaint is a crap function to me; I don't care about it and I only call it to satisfy Windows. In fact, the most recent thing I did was to kill the WM_PAINT case altogether. It didn't fix the problem, though.

    The problem lies in the relationships among the front DC, back DC, and compatible bitmap. In the code above, I'm making the back bitmap compatible with the front DC, but selecting it into the back DC. This works fine when it comes to all the colours coming out right, but there is the problem with the slowing of the cursor. Alternatively I can make the back bitmap compatible with the back DC, in which case the program operates smoothly but the colours are all screwed up.

    I understand the concept of the third DC, which is the memory DC I select the bitmaps into before blitting into the back DC. But, truth be told, I haven't even gotten to that stage yet; I'm still just drawing text, in which case I'm drawing the text directly into the backDC, the same as I would have drawn into the front DC without DB.

  10. #10
    Registered User
    Join Date
    Jan 2003
    Posts
    35
    Well well well. I've finished implementing double buffering, and what a cake it bakes, too!

    Programming is a battlefield and I actually managed to crash my computer by getting my graphics driver caught in an infinite loop ... or so said the message on startup. But after all that, the problem just seemed to be the speed at which I was blitting. In the main part of my program, the frame is only drawn once every 10ms. The text part (the menu and instructions) used to be drawn only when strictly necessary, back before DB. When I implemented DB I let it draw constantly, with no delay at all, and that's what made the cursor slow outside the program.

    Hmm ... well, this doesn't explain absolutely everything. Still don't know why making the backDC bitmap compatible with the backDC stopped the cursor problem. Maybe the timing delay is hiding rather than solving the problem. But for right now I don't give a flip...

    So there you go, I'm all double buffered! Thanks, folks!

  11. #11
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    >>There is no DC returned from BeginPaint.

    Check the return type/variable in the manual/help. Look at the elements of the PAINTSTRUCT. Note the DC is the same and is the area that windows has been told needs to be repainted ie is invalid. That is why you need to process these messages, the call to BeginPaint() validates this area and removes it from the msg cue.

    >>BeginPaint is a crap function to me;

    Not all msg's to redraw your screen come from within your app.

    >>The problem lies in the relationships among the front DC, back DC, and compatible bitmap

    Because you are trying to use the same one for two tasks. In reality it is the screen DC belonging to windows and not your apps.


    But hey........... I just do this for a living. I'm sure you know best.
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  12. #12
    Registered User
    Join Date
    Jan 2003
    Posts
    35
    Code:
    void Bitmaps::Init(HWND hw )
    {
       init=true;
       hwnd = hw;
    
       windowRect = new RECT;
       GetClientRect(hwnd, windowRect );
    
       hdc = GetDC(hwnd );
       hdcBuf = CreateCompatibleDC(hdc );
       hdcMem = CreateCompatibleDC(hdcBuf );
    
       hbmBackBM = CreateCompatibleBitmap(hdc, windowRect->right, windowRect->bottom );
       hbmOldBackBM = (HBITMAP)SelectObject(hdcBuf, hbmBackBM );
    
       BitBlt(hdcBuf, 0, 0, windowRect->right, windowRect->bottom, NULL, 0, 0, BLACKNESS );
       BitBlt(hdc, 0, 0, windowRect->right, windowRect->bottom, NULL, 0, 0, BLACKNESS );
    }
    
    
    void Bitmaps::BlitImage(
                    int imageX,
                    int imageY,
                    HBITMAP hbmImage,
                    bool erase)
    {
       oldBitmap = (HBITMAP)SelectObject(hdcMem, hbmImage);
    
       if(erase)
          BitBlt(hdcBuf, imageX, imageY, windowRect->right, windowRect->bottom, hdcMem, 0, 0, SRCAND);
       else
          BitBlt(hdcBuf, imageX, imageY, windowRect->right, windowRect->bottom, hdcMem, 0, 0, SRCPAINT);
    
       SelectObject(hdcMem, oldBitmap);
    }
    
    void Bitmaps::SwapBuffers()
    {
       BitBlt(hdc, 0, 0, windowRect->right, windowRect->bottom, hdcBuf, 0, 0, SRCCOPY );
       BitBlt(hdcBuf, 0, 0, windowRect->right, windowRect->bottom, NULL, 0, 0, BLACKNESS );
    }
    
    Bitmaps::~Bitmaps()
    {
       if(init)
       {
          SelectObject(hdcBuf, hbmOldBackBM );
          DeleteObject(hbmBackBM );
    
          ReleaseDC(hwnd, hdcBuf );
          ReleaseDC(hwnd, hdcMem );
          ReleaseDC(hwnd, hdc );
    
          delete windowRect;
       }
    }
    Explain to me what's wrong with this. You're being too abstract for me; where exactly am I using the same DC to do two things?

  13. #13
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    Without the code for WM_PAINT / OnPaint() I can't tell. That is;

    Where do you draw to the screen and tell windows the update region is now valid?


    >>When I implemented DB I let it draw constantly, with no delay at all, and that's what made the cursor slow outside the program.<<

    Windows will tell you when you need to redraw (or your app will call for a paint after SwapBuffers() ect )



    but;

    >>ReleaseDC(hwnd, hdcBuf );
    ReleaseDC(hwnd, hdcMem );<<

    You can only ReleaseDC() a DC you got from GetDC() you have to DeleteDC() ones you create. So you have a GDI mem leak here.


    >>if(init)
    better to test if the hdc == NULL as creation may have failed


    >>BlitImage

    imageX and imageY are to crop the image? (no comments)
    As these are used as the starting point for the BitBlt(). That is the image will be not be drawn from 0 - imageX and 0 - imageY. And will be drawn from imageX - Window.right ect


    A standard
    WM_PAINT (or OnPaint() if you convert) handler

    Code:
    //find update area
    GetUpdateRect(hWnd, &rClientRect, 0);
    if (IsRectEmpty(&rClientRect))
          GetClientRect(hWnd, &rClientRect);
    
    BeginPaint(hWnd, &ps);
    //or hdc=BeginPaint(hWnd, &ps);
    BitBlt(ps.hdc, rClientRect.left,  rClientRect.top,  rClientRect.right -  rClientRect.left,  rClientRect.bottom - rClientRect.top,
      hdcScreen, rClientRect.left,  rClientRect.top, SRCCOPY);
    EndPaint(hWnd, &ps);
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  14. #14
    Registered User
    Join Date
    Jan 2003
    Posts
    35
    Edited post:

    Disregard the earlier post. Novocain, I got your method to work. Trouble is, the result is exactly the same as it was before! I can't detect any difference. It acts as if something is holding up the message queue -- the controls lag. When I press an arrow key, there's a second or so delay before the character responds to it. The delay is the same for all keys, e.g. when I press escape there's a delay before the close dialog appears, etc.

    So now I'm stumped. When the window is displaying only text (e.g. menu, instructions), there is no lag.

    BTW I released the orignal DC at the end of the Init function as you suggested. So now the DC I'm blitting into is the ps.hdc. The DC I'm blitting from is the hdcBuf.
    Last edited by fusikon; 02-13-2003 at 08:05 PM.

  15. #15
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    >> It acts as if something is holding up the message queue<<

    How do you call for a paint msg?


    I use a single InvalidateRect() in my version of the swap buffers.

    If you are using PeekMessage() in the message loop / pump then you may need another when there is no msg and drawing has occured.

    UpdateWindow() is similar but I prefer to specify the region to be updated.
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. No Match For Operator+ ???????
    By Paul22000 in forum C++ Programming
    Replies: 24
    Last Post: 05-14-2008, 10:53 AM
  2. Conversion From C++ To C
    By dicon in forum C++ Programming
    Replies: 2
    Last Post: 06-10-2007, 02:54 PM
  3. Need some help/advise for Public/Private classes
    By nirali35 in forum C++ Programming
    Replies: 8
    Last Post: 09-23-2006, 12:34 PM
  4. newbie needs help with code
    By compudude86 in forum C Programming
    Replies: 6
    Last Post: 07-23-2006, 08:54 PM
  5. double buffering and page flipping question
    By stupid_mutt in forum C Programming
    Replies: 2
    Last Post: 01-30-2002, 01:50 PM