   [Win32 C++] HDC draw limit? Overloaded?
  HelloI have made a simple 2d Game engine in Win32 C++. I use double buffering to draw the objects onto the client HDC. But I think I maybe drawing too many times to a HDC causing an error or something?I have the executable that really simply shows my problem so I really encourage you to download it instead of having to read all the below. It shows exactly where the drawing fails at loop 50 (see the console window). PS how do you add links in this forum?http://www.mediafire.com/?gz0xzktmrytThe 2d engine works pretty well, each object controls its own movement then informs the 2d engine saying "Draw me, Audio me, Keep me in the action/input queue etc." And the 2d engine draws an object's new position every loop well.But One funny problem I am getting is: - If I have n objects to draw, after the n*100th gameloop the drawing onto the window HDC of each objects new coordinates stops. The game loop is still looping perfectly, the actual code/function to draw the objects onto the Buffer hdc still runs(I checked), the function to copy the BufferHDC to the WindowHDC still runs BUT the window is not displaying the new HDC image therefore each objects new position(they are moving).So in the case when I have 5 objects to draw & they all are moving to the same point on the window (100,100):- For the 1st 500 game loops the 2d engine draws all the objects (every loop) perfectly(in their new position, coloured etc.) But then the window doesn't get updated after the 501st loop?In the case when I have 55 objects to draw & they all are moving to the same point on the window (100,100):- For the 1st 50 game loops the 2d engine draws all the objects (every loop) perfectly(in their new position, coloured etc.) But then the window doesn't get updated after the 51st loop & the window HDC just continually shows the HDC image of the 50th frame?So after that long winded explaination I have come to believe that maybe I can only draw on a HDC so many times OR I can only copy to the window HDC so many times OR maybe I am overloading the variables or system concerned with copying the BufferHDC to the WindowHDC?My algorithm is simple:- The graphic engine has a draw queue that contains all the objects that need to be drawn in the next game loop- When the next loop begins I call the function ExecuteDrawLogic(). Where every object inside the Draw Queue is told "I am passing you the BufferHDC, draw over your old position with the bK colour, draw yourself at your new position" then I remove the object from the queue.- After all objects have done their drawing I copy the BufferHDC to the main WindowHDC which has/shows each objects new position & makes it look they r moving.So again after this really long explanation, do you know why the WindowHDC does not get updated after nth game loop? Am I overloading the bufferHDC or something?Graphic Engine Class:class GraphicEngine{ public:   GraphicEngine( Engine2d *ParentEng, HWND Hwnd );~GraphicEngine();void StoreSiblingEngines( SystemEngine *SystemEng,  InputEngine *InputEng,  AudioEngine *AudioEng );void SetHwnd( HWND Hwnd );void CopyWindowHDC( HWND Hwnd, HDC WindowHDC );void Clear();int  AddToDrawQueue( Object *Obj );bool ExecuteDrawLogic( HDC WindowHDC );  private:    HWND hwnd;RECT ClientRect;HDC BufferCanvas;HBITMAP hBlt;HGDIOBJ hMCBmp;queue <Object*> DrawQueue;};Functions associated with double buffering:void GraphicEngine :: CopyWindowHDC( HWND Hwnd, HDC WindowHDC){   // Post: Copy Window HDC to Buffer canvas. This function is done once, when   //  the 1st WM_PAINT message is sent when the window is created. It also is   //  called again if the window is resized/minimised etc.    GetClientRect( Hwnd, &ClientRect ); BufferCanvas = CreateCompatibleDC( WindowHDC );   hBlt   = CreateCompatibleBitmap( WindowHDC, ClientRect.right, ClientRect.bottom );   hMCBmp = SelectObject( BufferCanvas, hBlt ); BitBlt(BufferCanvas, ClientRect.left, ClientRect.top, ClientRect.right, ClientRect.bottom, WindowHDC, 0, 0, SRCCOPY );}bool GraphicEngine :: ExecuteDrawLogic( HDC WindowHDC ){   // Post: Draw all 'Dirty' Objects onto window using Double Buffering (avoids   //  flicker affect). hMCBmp = SelectObject( BufferCanvas, hBlt ); // Draw objects onto Buffer Canvas   while ( !DrawQueue.empty() )   {   DrawQueue.front()-> EraseSelf( BufferCanvas );   DrawQueue.front()-> DrawSelf( BufferCanvas );   DrawQueue.pop();   }    // Copy BufferCanvas HDC to WindowCanvas HDC   BitBlt( WindowHDC, ClientRect.left, ClientRect.top, ClientRect.right,  ClientRect.bottom, BufferCanvas, 0, 0, SRCCOPY );   }int GraphicEngine :: AddToDrawQueue( Object *Obj ){   // Post: Add Object to Graphic Engine's draw queue. At the next clock step   //  the Graphic Engine will call this objects' DrawSelf() function // TODO: Arrange/Add Objects in queue according to depth eg an airplane should    //  be drawn last coz its higher DrawQueue.push( Obj );}PS. I tested my engine by taking out drawing to a BufferHDC & I just tried drawing everything to the main window HDC directly & the same thing happens at the exact same moment. So maybe its the way I send WM_PAINT evey gameloop so it will call the ExecuteDraw() function?bool GraphicEngine :: ExecuteDrawLogic( HDC WindowHDC ){   // Post: Draw all 'Dirty' Objects onto window using Double Buffering (avoids   //  flicker affect). hMCBmp = SelectObject( BufferCanvas, hBlt ); // Draw objects onto Buffer Canvas   while ( !DrawQueue.empty() )   {   // Drawing directly to the window causes the same effect   DrawQueue.front()-> EraseSelf( WindowHDC ); //BufferCanvas );   DrawQueue.front()-> DrawSelf( WindowHDC ); //BufferCanvas );   DrawQueue.pop();   }    // Copy BufferCanvas HDC to WindowCanvas HDC   //BitBlt( WindowHDC, ClientRect.left, ClientRect.top, ClientRect.right,    //   ClientRect.bottom, BufferCanvas, 0, 0, SRCCOPY );   }// Function/code to call Exectute draw.Works by sending a WM_PAINT message to window// Where the ExectuteDraw() function will them be called. Maybe I am // overloading the window queue?case WM_TIMER:   {   switch(LOWORD(wParam)) {  case DE_CLOCKTICK:   {   num++;   cout << "Game Loop No. " << num << " \n"; GameEngine.PerformLogicOperations(); // move objects // Send WM_PAINT message to call ExecuteDraw();   InvalidateRect( hwnd, NULL, false );   UpdateWindow( hwnd ); } break;    default:   break;   }   }   break;   case WM_PAINT:   {   HDC hdc;   PAINTSTRUCT ps;   hdc = BeginPaint(hwnd, &ps); if (FirstPaint)    {  GameEngine.SetWindowAttributes( hwnd, hdc );  FirstPaint = false;} GameEngine.PerformDrawOperations( hdc ); EndPaint(hwnd, &ps);   }   break; [Edited by - gretty on June 7, 2010 9:02:57 PM]
  It's been many years since I've worked with Win32 DCs but I'll give this one a go.From what I remember DCs (and GDIs in general) were a limited resource so there is probably a 'DC' leak somewhere in the program.Check the return values from all the Win32 functions. You may have just removed them from your post for brevity purposes and that's ok. A quick look at GraphicEngine :: ExecuteDrawLogic() and it looks like you're selecting objects into the DC without releasing them.[Edit]CopyWindowHDC() also.Think about SelectObject() as if your were allocating memory. It takes another paired call to SelectObject() to release that memory.Below I moved the BufferCanvas to the draw loop, it may work fine the way you have it.bool GraphicEngine :: ExecuteDrawLogic( HDC WindowHDC ){   // Post: Draw all 'Dirty' Objects onto window using Double Buffering (avoids   //  flicker affect).   HDC BufferCanvas = CreateCompatibleDC(WindowHDC );if(NULL == myHDC)return false;//Do this check for all the remaining win32 calls   hMCBmpOld = SelectObject( BufferCanvas, hBlt ); // Draw objects onto Buffer Canvas   while ( !DrawQueue.empty() )   {   DrawQueue.front()-> EraseSelf( BufferCanvas );   DrawQueue.front()-> DrawSelf( BufferCanvas );   DrawQueue.pop();   }    // Copy BufferCanvas HDC to WindowCanvas HDC   BitBlt( WindowHDC, ClientRect.left, ClientRect.top, ClientRect.right,  ClientRect.bottom, BufferCanvas, 0, 0, SRCCOPY );SelectObject(BufferCanvas ,hMCBmpOld );DeleteDC(BufferCanvas );//paired with CreateCompatibleDC()return true;}In the WindowProc callback timer handler,InvalidateRect() posts the WM_PAINT message to the window's message queue and UpdateWindow() calls the WindowProc directly with the WM_PAINT message. So it may not be necessary to call them both.   ;  Your CopyWindowHDC function leaks two GDI objects every time it's called.  Neither of your functions clean up after the SelectObject calls before they exit.  It's hard to tell if you clean up somewhere else but this sort of problem is extremely common so I'm expecting you don't.   ;  Thanks for your replies guys :DTo sum up what has been said...Are you saying that for every SelectObject or CreateCompatibleX() I need to then release the data by doing DeleteObject() & DeleteDC()?I do do this in the GraphicEngine's destructor only because I thought that if I did it after every function I would destroy the information in the Bitmap, GDI object & the HDC?So would this below be correct? Any suggestions & advice on changes to the code would be really appreciated.I am wary of using the DeleteObject because  am unsure if I am deleting stuff I need(although this function releases the object(unselects) as opposed to deleting its information?).void GraphicEngine :: CopyWindowHDC( HWND Hwnd, HDC WindowHDC){   // Post: Copy Window HDC to Buffer canvas. This function is done once, when   //  the 1st WM_PAINT message is sent when the window is created. It also is   //  called again if the window is resized/minimised etc.    GetClientRect( Hwnd, &ClientRect ); BufferCanvas = CreateCompatibleDC( WindowHDC );   hBlt   = CreateCompatibleBitmap( WindowHDC, ClientRect.right, ClientRect.bottom );   hMCBmp = SelectObject( BufferCanvas, hBlt ); BitBlt(BufferCanvas, ClientRect.left, ClientRect.top, ClientRect.right, ClientRect.bottom, WindowHDC, 0, 0, SRCCOPY );   // @Anon Mike: When you say I have 2 leaks which of these 3 vars are you referring to? DeleteObject(hMCBmp);   DeleteObject(hBlt);  // wont this   DeleteDC(BufferCanvas); // & this cause errors?}bool GraphicEngine :: ExecuteDrawLogic( HDC WindowHDC ){   // Post: Draw all 'Dirty' Objects onto window using Double Buffering (avoids   //  flicker affect). hMCBmp = SelectObject( BufferCanvas, hBlt ); // Draw objects onto Buffer Canvas   while ( !DrawQueue.empty() )   {   DrawQueue.front()-> EraseSelf( BufferCanvas );   DrawQueue.front()-> DrawSelf( BufferCanvas );   DrawQueue.pop();   }    // Copy BufferCanvas HDC to WindowCanvas HDC   BitBlt( WindowHDC, ClientRect.left, ClientRect.top, ClientRect.right,  ClientRect.bottom, BufferCanvas, 0, 0, SRCCOPY );    DeleteObject(hMCBmp); // do I delete the other vars too?   };  Beyond the resource leaks, which kill your program in the long term, you have some performance issues in the short term.You are creating and destroying resources needlessly.  Make one and keep it around.  Don't destroy it until the windows have changed.BitBlt can be a heavyweight function, especially for a full window.  Consider a "normal" 1080p widescreen monitor - 1920x1080.  That's a bit over 6MB of data.  A 6MB transfer by itself doesn't seem like much, but if you want 75Hz (a frequent preferred refresh rate) it adds up to 466.56MB per second for just that one line.  In this specific case you didn't perform any operations, but if you did use them, you'll have that much additional processing.  If your window spans multiple monitors there will be another performance hit.  Even worse, BitBlt and the related GDI functions are blocking calls.  They wait until all the data is rendered before you can continue your code.  You can't easily mix processing with rendering unless you do your own threading.You don't mention how bit your sprites are, but I imagine they involve another several megabytes of data transfer every frame.Add them up and your seemingly simple BitBlt operations can seriously stall your performance.Look at only updating the portions that change, rather than updating everything.  A little bit of 2D caching can go a long way in performance. In addition to only doing work if necessary, try to offload the work.  If you can manage it, consider moving to the Windows Presentation Foundation functions, which are part of the .net framework's System.Drawing.* classes.  Many of the functions are asynchronous, meaning they take place in the background so you can do other things.  They also take advantage of DirectX functionality which often gives better performance.   ;  Thanks, I didn't know that the BitBlt function would be so 'expensive' in terms of data so thats good to know. I also watched my application through the task manager & I knowticed that it keeps grabbing/increasing in memory usage very fast so that might be the main reason.I am new to all this stuff, so I want to make a working 2d engine before I try to make it the most efficient. But I would like to (later on) create a BufferHDC that covers only the area needed to be drawn over then BitBlt that Bitmap to the main HDC.For now, is my code the correct way to avoid those memory leaks you guys pointed out(this is a hard area for me - Selecting/DeletingObjects)? Do you believe if I cover these memory leaks that the main problem(draw freezing) will be solved?void GraphicEngine :: CopyWindowHDC( HWND Hwnd, HDC WindowHDC){   // Post: Copy Window HDC to Buffer canvas. This function is done once, when   //  the 1st WM_PAINT message is sent when the window is created. It also is   //  called again if the window is resized/minimised etc.    GetClientRect( Hwnd, &ClientRect ); BufferCanvas = CreateCompatibleDC( WindowHDC );   hBlt   = CreateCompatibleBitmap( WindowHDC, ClientRect.right, ClientRect.bottom );   hMCBmp = SelectObject( BufferCanvas, hBlt ); BitBlt(BufferCanvas, ClientRect.left, ClientRect.top, ClientRect.right, ClientRect.bottom, WindowHDC, 0, 0, SRCCOPY );   // @Anon Mike: When you say I have 2 leaks which of these 3 vars are you referring to? DeleteObject(hMCBmp);   DeleteObject(hBlt);  // wont this   DeleteDC(BufferCanvas); // & this cause errors?}bool GraphicEngine :: ExecuteDrawLogic( HDC WindowHDC ){   // Post: Draw all 'Dirty' Objects onto window using Double Buffering (avoids   //  flicker affect). hMCBmp = SelectObject( BufferCanvas, hBlt ); // Draw objects onto Buffer Canvas   while ( !DrawQueue.empty() )   {   DrawQueue.front()-> EraseSelf( BufferCanvas );   DrawQueue.front()-> DrawSelf( BufferCanvas );   DrawQueue.pop();   }    // Copy BufferCanvas HDC to WindowCanvas HDC   BitBlt( WindowHDC, ClientRect.left, ClientRect.top, ClientRect.right,  ClientRect.bottom, BufferCanvas, 0, 0, SRCCOPY );    DeleteObject(hMCBmp); // do I delete the other vars too?   };Quote:Original post by grettyAre you saying that for every SelectObject or CreateCompatibleX() I need to then release the data by doing DeleteObject() & DeleteDC()?You will only want to delete objects that you created yourself. The object returned by SelectObject() may not be yours to delete. Instead of DeleteObject(), you will want to use SelectObject() to return the DC back to the way you found it.Think of DCs as limited shared resources that must be put back exactly the way you found them. Generally they're created just before they're needed and immediately freed afterwards. This allows other programs on the computer to successfully allocate DCs.init  //create an object and a dc that the program owns  myobject = createcompatiblebitmap()  mydc = createcompatibledc()draw(windowdc)   //attach bitmap to dc  previousobject = selectobject(mydc,myobject)    //draw using mydc   //blit mydc to windowdc   //cleanup, also releases any resource from the previous call   selectobject(mydc,previousobject)close   //free object and dc once the program is finished with them   deletedc(mydc)   deleteobject(myobject);  Thanks its starting to make sense. This is what I have changed, although the same thing happens at frame 50, the HDC is no longer copied & frame 50 is continually displayed?void GraphicEngine :: CopyWindowHDC( HWND Hwnd, HDC WindowHDC){   // Post: Copy Window HDC to Buffer canvas. This function is done once, when   //  the 1st WM_PAINT message is sent when the window is created. It also is   //  called again if the window is resized/minimised etc.    GetClientRect( Hwnd, &ClientRect ); BufferCanvas = CreateCompatibleDC( WindowHDC );   hBlt   = CreateCompatibleBitmap( WindowHDC, ClientRect.right, ClientRect.bottom );   hMCBmp = SelectObject( BufferCanvas, hBlt ); BitBlt(BufferCanvas, ClientRect.left, ClientRect.top, ClientRect.right, ClientRect.bottom, WindowHDC, 0, 0, SRCCOPY );   // New code: Is this correct   SelectObject( BufferCanvas, hMCBmp );   DeleteDC(BufferCanvas);}bool GraphicEngine :: ExecuteDrawLogic( HDC WindowHDC ){   // Post: Draw all 'Dirty' Objects onto window using Double Buffering (avoids   //  flicker affect). BufferCanvas = CreateCompatibleDC( WindowHDC ); if (BufferCanvas == NULL)   {   return false;    } HGDIOBJ hMCBmpOld = SelectObject( BufferCanvas, hBlt ); // Draw objects onto Buffer Canvas   while ( !DrawQueue.empty() )   {   DrawQueue.front()-> EraseSelf( BufferCanvas );   DrawQueue.front()-> DrawSelf( BufferCanvas );   DrawQueue.pop();   }    // Copy BufferCanvas HDC to WindowCanvas HDC   BitBlt( WindowHDC, ClientRect.left, ClientRect.top, ClientRect.right,  ClientRect.bottom, BufferCanvas, 0, 0, SRCCOPY );  SelectObject( BufferCanvas, hMCBmpOld );   DeleteDC( BufferCanvas ); return true;};  What errors are you getting from SelectObject/BitBlt/DeleteObject? Please check all of them. Some GDI functions use GetLastError() to return extended error info. Make sure CopyWindowHDC() is only being called once.Anything interesting happening inside EraseSelf/DrawSelf?Log all major events and be creative in your debugging.   ;Quote:Original post by grettyI also watched my application through the task manager & I knowticed that it keeps grabbing/increasing in memory usage very fast so that might be the main reason.Ugh "Task Manager" (shudders)No Windows developer should be without "Process Explorer" from Microsoft technet (formerly www.sysinternals.com) direct link, otherwise you're programming in the dark ages.In there just add columns for Handles, USER and GDI objects, and you'll see the problem right away. Your app will rapidly reach 9999 in one of those columns and then just sit there.Although it's great that you wrote up a very informative first post, having had this happen before, I basically already knew the cause after only reading the thread title!