🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Making a double buffer global

Started by
4 comments, last by Airbatz 4 years, 3 months ago

I don't want to clutter up the other thread, so I made a new one just for this topic. Can I please get an explanation of making a 'global' double buffer? I'm having trouble visualising the implementation.

From what I've been able to gather and as it's been pointed out, my code is creating / destroying the bitmap used as the buffer each time the DrawMain() function gets called. I've come up with the following; modified from my previous attempt. Going by my understanding, I put hdcBuffer = CreateCompatibleDC( hdc ); and destroy the buffer on exit.

If I still haven't gotten it right, I want to know! ?

HDC hdc;
HDC hdcMem;
HDC hdcBuffer;
HBITMAP hbmBuffer;
HBITMAP hbmOldBuffer;
PAINTSTRUCT ps;
RECT rcClient;

Globals at the top of the program.

void DrawMain( HDC hdc, RECT* rcClient )
{
  hbmBuffer    = CreateCompatibleBitmap( hdc, rcClient->right, rcClient->bottom );
  hbmOldBuffer = (HBITMAP)SelectObject( hdcBuffer, hbmBuffer );

  FillRect(hdcBuffer, rcClient, (HBRUSH)GetStockObject(WHITE_BRUSH));

  // title
  SelectObject( hdcMem, g_hbmTitle ); //not sure if HBITMAP hbmOld = (HBITMAP) is necessary as the program works without it
  TransparentBlt( hdcBuffer, 50, 24, 297, 39, hdcMem, 0, 0, 297, 39, RGB( 255, 0, 255 ) );
   // background
  SelectObject( hdcMem, g_hbmBackground );
  TransparentBlt( hdcBuffer, 112, 90, 186, 91, hdcMem, 0, 0, 186, 91, RGB( 255, 0, 255 ) );

  // sprite
  HBITMAP hbmOld3 = (HBITMAP)SelectObject( hdcMem, g_hbmSprite );
  TransparentBlt( hdcBuffer, Sprite_Slow.x+(Sprite_Slow.amplitude*sin(Sprite_Slow.angle)+0.5), Sprite_Slow.y,15, 15 , hdcMem, 15*(frames%28), 0, 15, 15, RGB( 255, 0, 255 )) ;

  // blit double buffer to window
  BitBlt( hdc, 0, 0, rcClient->right, rcClient->bottom, hdcBuffer, 0, 0, SRCCOPY );

  SelectObject( hdcMem, hbmOld3 );
  DeleteDC( hdcMem );
}

The updated DrawMain() function.

 case WM_PAINT:
      {
        hdc = BeginPaint( hwnd, &ps );

        GetClientRect( hwnd, &rcClient );
        hdcBuffer    = CreateCompatibleDC( hdc );
        hdcMem       = CreateCompatibleDC( hdc );

        dw_timestart = timeGetTime();

        DrawMain( hdc, &rcClient );
        AnimateFrames ();
        UpdateSpritePosition();

        dw_timestop = timeGetTime();
        EndPaint( hwnd, &ps );

        f_cycle_t = dw_timestop - dw_timestart;
      }
      return 0;

The updated WM_PAINT message.

case WM_DESTROY:

      SelectObject( hdcBuffer, hbmOldBuffer );
      DeleteDC( hdcBuffer );
      DeleteObject( hbmBuffer );
      DeleteObject( g_hbmSprite );
      DeleteObject( g_hbmTitle );
      DeleteObject( g_hbmBackground );

      PostQuitMessage( 0 );
      return 0;

How I'm destroying the bitmap. The program freezes after a few moments.

None

Advertisement

Sounds like you are trying something similar to what I do at the moment but in a different language so maybe this is useful. I'm using C# and the .Net managed code only (no p/invoke at all) but under the hood, it uses the same functions you have encapsulated into the .NET Framework classes.

For my UI Framework, I wanted to have full control over the rendering so I stripped down a System.Windows.Forms.Form to the bare minimum and also use the WMMESSAGE pump to handle rendering. First I created a System.Drawing.Graphics object of my form which is calling GdipCreateFromHWND WINAPI function and save the result. This is the target I copy my buffer to via GdipDrawImageI WINAPI function, whenever WM_PAINT is pushed. After copy is done, I call the default window procedure that is I don't know exactly, but calling ValidateRect or similar I guess.

My render buffer is an array of bytes in heap memory that I created a System.Drawing.Bitmap object from. I push it to the constructor but under the hood it calls GdipCreateBitmapFromScan0 WINAPI function. Then I create again a graphics object from that bitmap and save it to be used for all my draw-calls. So drawing happens to the byte buffer in memory and on WM_PAINT I simply copy the contents from heap to Windows.

Why I preserve the handles I obtain from the GDI calls (the graphics objects) is simply to save some performance on creating them over and over again. I discard those handles at the time when my window resizes because they cover only the current size at the moment they were created, or the window gets discarded, then I discard all these handles and the buffer memory.

I tested it and it works quiet well, next will be implementing a buffer swap to be able to handle rendering in multiple threads to squeeze the last bit out of GDI+

@Shaarigan

Hello Shaarigan.

Thank you for your reply. I am using C and GDI for this project. I'm afraid I know barely anything about .NET and have never used C# so much of this goes over my head! I'm guessing that the method for achieving the same things in GDI+ is similar, within reason.

I do have a question about performance. My program uses between 27 and 34% CPU, but nothing extremely demanding is going on. Is this normal? The GDI Objects column also shows the number of objects changing intermittently between 14 and 18. I'm wondering if something is leaking/not being released correctly.

I have made some progress on the initial problem, however one thing puzzling me is why the code deleting the DC and Object has to be within the DrawMain() function. If I put it in WM_DESTROY, I get a blank window.

void DrawMain()
{
  SelectObject( hdcMem, g_hbmTitle ); //not sure if HBITMAP hbmOld = (HBITMAP) is necessary
  TransparentBlt( hdcBuffer, 50, 24, 297, 39, hdcMem, 0, 0, 297, 39, RGB( 255, 0, 255 ) );

  SelectObject( hdcMem, g_hbmBackground );
  TransparentBlt( hdcBuffer, 112, 90, 186, 91, hdcMem, 0, 0, 186, 91, RGB( 255, 0, 255 ) );

  SelectObject( hdcMem, g_hbmSprite );
  TransparentBlt( hdcBuffer, Sprite_Slow.x+(Sprite_Slow.amplitude*sin(Sprite_Slow.angle)+0.5),
  Sprite_Slow.y,15, 15 , hdcMem, 15*(frames%28), 0, 15, 15, RGB( 255, 0, 255 )) ;

  // blit double buffer to window
  BitBlt( hdc, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, hdcBuffer, 0, 0, SRCCOPY );

  // This code gives produces a blank window when placed in WM_DESTROY
  DeleteDC( hdcMem );
  SelectObject( hdcBuffer, hbmOldBuffer );
  DeleteDC( hdcBuffer );
  DeleteObject( hbmBuffer );
}
    case WM_PAINT:
      {
        hdc = BeginPaint( hwnd, &ps );

        GetClientRect( hwnd, &rcClient );
        hdcBuffer    = CreateCompatibleDC( hdc );
        hdcMem       = CreateCompatibleDC( hdc );

        dw_timestart = timeGetTime();

        hbmBuffer    = CreateCompatibleBitmap( hdc, WINDOW_WIDTH, WINDOW_HEIGHT );
        hbmOldBuffer = (HBITMAP)SelectObject( hdcBuffer, hbmBuffer );
        FillRect(hdcBuffer, &rcClient, (HBRUSH)GetStockObject(WHITE_BRUSH));

        DrawMain();
        DrawBackroundBrush(); //placeholder for drawing effect
        AnimateFrames ();
        UpdateSpritePosition();

        dw_timestop = timeGetTime();
        EndPaint( hwnd, &ps );

        f_cycle_t = dw_timestop - dw_timestart;
      }
      return 0;

I've moved all the DC creation to WM_PAINT and the functions don't need any parameters, unlike before.

case WM_DESTROY:

      DeleteObject( g_hbmSprite );
      DeleteObject( g_hbmTitle );
      DeleteObject( g_hbmBackground );

      PostQuitMessage( 0 );
      return 0;

None

Airbatz said:
I'm afraid I know barely anything about .NET and have never used C#

This is why I posted the links to underlaying code that is wirtten using C/C++ WINAPI calls and pointer code. It might be worth looking at it.

As far as I know, in the old days of GDI, you needed to release GDI objects after you were done using them for one simple reason, memory. Every GDI object uses some amount of graphics memory and that is limited per process. So if you are drawing complex UIs, you'll likely run out of memory very quickly.

If your program uses such high CPU even if you don't do anything, try investigating how often your WM_PAINT code is called. Maybe you are missing something and Windows dosen't accept your paint as a validation of the update rect. In this case your message pipeline is flooded with more WM_PAINT messages over and over again

@Shaarigan

I have taken a look at the links but it will take a while before I can make much sense of it as a whole. Currently I'm just modifying the program step by step to see how it affects the performance. Visibly nothing is different. Thanks for your input, I will read up on WM_PAINT and hopefully find out what's going on.

I am deleting the objects on program exit, but maybe not giving the memory back to the system.. I will take your advice and examine the calls to WM_PAINT.

None

This topic is closed to new replies.

Advertisement