Given I implement double buffering in GDI:
static HDC hdc;
static HDC backDC;
static HBITMAP backBuffer;
static HGDIOBJ oldBitmap;
static RECT client;
case WM_CREATE:
hdc=GetDC(hWnd);
GetClientRect(hWnd, &client);
backDC=CreateCompatibleDC(hdc);
backBuffer=CreateCompatibleBitmap(hdc,client.right,client.bottom);
oldBitmap=SelectObject(backDC,backBuffer);
ReleaseDC(hWnd,hdc);
case WM_PAINT:
Rectangle(backDC, 0, 0,client.right,client.bottom); // displays rectangle the
size of client to draw on it
hdc = BeginPaint(hWnd, &ps);
BitBlt(hdc,0,0,client.right,client.bottom,backDC,0,0,SRCCOPY);
// TODO: Add any drawing code here...
EndPaint(hWnd, &ps);
How do I handle the resize ? One thing I have tried to do is
case WM_SIZE:
client.right=LOWORD(lParam);
client.bottom=HIWORD(lParam);
SendMessage(hWnd,WM_CREATE,NULL,NULL);
What it does is, once I get resize message with new client coordinates it sends message to WM_CREATE and it actually works.... HOWEVER ! It creates massive leak, because I basically create a new bitmap every time without destroying it. Can someone tell me if there is a better way to do it ? Thx
Keeping the backbuffer around is an optimization that's not always needed. You can create it in the WM_PAINT handler (to the size of GetClientRect), paint to it, blit from it to the actual window DC, and clean up. No leaks. No distribution of functionality among all the message handlers. No global variables. Nice and clean.
If you do want to keep one around, I make a class. Constructor takes the size. Destructor cleans up everything. On WM_SIZE, construct a new one as a local stack variable, swap with the old one, and let destructor for the temp stack one clean up.
I would recommend making the back buffer a fixed size (typically, the size of the screen), and then using StretchBlt (or StretchDIBits if you're using DIBs) to render it in the proper size on the display surface.
That way, you never have to worry about reallocating the back buffer.
Related
The SDL wiki says of SDL_RenderPresent():
SDL's rendering functions operate on a backbuffer; that is, calling a rendering function such as SDL_RenderDrawLine() does not directly put a line on the screen, but rather updates the backbuffer. As such, you compose your entire scene and present the composed backbuffer to the screen as a complete picture. Therefore, when using SDL's rendering API, one does all drawing intended for the frame, and then calls this function once per frame to present the final drawing to the user. The backbuffer should be considered invalidated after each present; do not assume that previous contents will exist between frames. You are strongly encouraged to call SDL_RenderClear() to initialize the backbuffer before starting each new frame's drawing, even if you plan to overwrite every pixel.
Why is the backbuffer invalidated? I'd like to optimize rendering performance by only redrawing parts of the rendering target that need to be redrawn. How can I do this if the backbuffer is invalidated? The Win32 API allows one to redraw portions of the rendering target. Why not SDL?
When you call present SDL sends the backbuffer to be displayed and it swaps it out thus the new backbuffer is garbage and you have to redraw it again, this is done for performance reasons.
What I think you can do if you're not modifying the whole screen is try to minimize draw calls by using your own backbuffer and then presenting that, something along the lines of:
// we'll use this texture as our own backbuffer
SDL_Texture *buffer = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB888,
SDL_TEXTUREACCESS_TARGET, w, h);
SDL_SetRenderTarget(renderer, buffer); // all the draw calls go to buffer instead of the sdl backbuffer
SDL_RenderCopy(renderer, some_texture, src_rect, dst_rect); // for example this would draw to buffer
SDL_SetRenderTarget(renderer, NULL); // we set the renderer back so we can draw to the SDL backbuffer
SDL_RenderCopy(renderer, buffer, src_rect, dst_rect); // copy our own backbuffer in one call
SDL_RenderPresent(renderer);
With this you can minimize draw calls since you'll be updating your own backbuffer and then presenting that instead of redrawing the whole screen each time.
Hi I want to clear out the DrawText() and I don't know how to do it properly. I could simply SetTextColor to the color of my background but this is not elegant way to do it I think. I dont know maybe I could do something with a rectangle rc that holds my text.
I draw by case WM_PAINT and in it:
hdc=BeginPaint(hwnd, &paint);
.... //some other code here
DrawText(hdc, TEXT("some text"), -1, &rc, DT_SINGLELINE);
....//some other code here
EndPaint(hwnd, &paint);
and one more thing. I don't have DeleteDC(hdc); or ReleaseDC(hdc); in my WM_PAINT is that ok, or I should have them after or before EndPaint(hwnd, &paint);?
There's no way to "clear" text that you've drawn other than to draw something else over the top. If your background is a solid color then just draw a rectangle of that color (you can work out how big it needs to be by using the DT_CALCRECT flag with DrawText). If your background is an image then you need to blit the appropriate area of the image.
Note that drawing the text over the top of itself using the background color (as you suggest) may not work because of ClearType/anti-aliasing.
To answer your second question, no - the DC returned by BeginPaint is effectively deleted by the call to EndPaint and so you don't need to (and mustn't) delete it separately.
MSDN and numerous post have suggested that BeginPaint/EndPaint should used in WM_PAINT. I've also seen numerous places suggesting that if double buffering is employed in painting, it makes more sense to initialize the DC and mem allocation in WM_CREATE and reuse those handles in WM_PAINT.
For example, using BeginPaint, I commonly see:
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
MemDC = CreateCompatibleDC(hdc);
bmp = CreateCompatibleBitmap(hdc, width, height);
oldbmp = SelectObject(MemDC,bmp);
g = new Graphics(MemDC);
//do paint on bmp
//blt bmp back to hdc
EndPaint(hWnd, &ps);
DeleteObject(bmp);
g->ReleaseHDC(MemDC);
DeleteDC(MemDC);
delete g;
To save the initialization and tearing down, is it possible to do this :
case WM_CREATE:
hdc = GetDC(hWnd);
//create memDC and graphics object references ...
case WM_DESTROY
//delete memDC and graphics object references...
case WM_PAINT
BeginPaint(hWnd, &ps);
//use previously create mem and graphics object to paint
EndPaint(hWnd, &ps);
So we only use EndPaint to clear the update region but delegate the drawing to prev created objects.
No, that's a very poor practice. This goes wrong first by the device context having the wrong clipping region, you cannot properly paint the window when the user resizes the window.
Second problem is that the update region clipping won't be in effect, PAINTSTRUCT.rcPaint. Preventing any possible paint optimization that Windows may automatically perform when the update region is just a part of the window.
Third problem is that you are holding on an operating system resource needlessly. All GDI objects are allocated in a single heap that's shared by all processes that run on the same desktop.
Creating a HDC is very cheap, create it when you need it. Not to mention the PAINTSTRUCT.hdc you'll get served on a platter, ready to use.
I have subclassed a graphics control that takes a device context handle, HDC, as an input and uses that for drawing. My new control is just the original control centered on top of a larger image. I would like to be able to call the original control's Draw() method for code re-use, but I'm unsure how to proceed.
Here's the idea:
void CCheckBox::DrawCtrl( HDC hdc, HDC hdcTmp, LPSIZE pCtlSize, BYTE alpha ) {
// original method draws a checkbox
}
void CBorderedCheckBox::DrawCtrl( HDC hdc, HDC hdcTmp, LPSIZE pCtlSize, BYTE alpha ) {
// Draw my image here
// Create new hdc2 and hdcTemp2 which are just some portion of hdc and hdcTemp
// For example, hdc2 may just be a rectangle inside of hdc that is 20 pixels
// indented on all sides.
// Call CCheckBox::DrawCtrl() with hdc2 and hdcTemp2
}
I think you may be confused of what a device context is. A device context is a place in memory that you can draw to, be it the screen buffer or a bitmap or something else. Since I imagine you only want to draw on the screen, you only need one DC. To accomplish what you want, I would recommend passing a rectangle to the function that tells it where to draw. Optionally, and with poorer performance, you could create a new Bitmap for the smaller area, and give the function the Bitmap's DC to draw on. Now that I think about it, that might have been what you meant in the first place :P Good luck!
While not foolproof, you can fake a DC as a subsection of a DC by using a combination of SetViewportOrgEx and SelectObject with a region clipped to the sub area in question.
The problem with this approach is if drawing code already uses these APIs it needs to rewritten to be aware that it needs to combine its masking and offsetting with the existing offsets and clipping regions.
I'm writing a simple automated test application for Win32. It runs as a separate process and accesses the target application via the Windows API. I can read window hierarchies, find labels and textboxes, and click buttons by sending/posting messages etc. All fine.
Unfortunately many controls in the target application are composed of nothing more than an owner-drawn control/window. (For example, we use the BCG menus and controlbars). Finding the correct part of the control to send a 'click' to is problematic.
Is there any way, given a HWND, to extract GDI drawing commands? I'd like to know each piece of text drawn to that control, and its coordinates.
Failing that, is there any way to capture a single control/window (again by HWND) into a bitmap? Worse-case scenario, I could OCR it.
You can use TextGRAB SDK to do this. Unfortunately it is shareware and costs $30.
To capture a window as a bitmap:
RECT rc;
GetClientRect(hWnd, &rc);
int cx = rc.right-rc.left;
int cy = rc.bottom-rc.top;
HDC winDC = ::GetDC(hWnd);
HDC tempDC = ::CreateCompatibleDC(winDC);
HBITMAP newBMP = ::CreateCompatibleBitmap(winDC, cx, cy);
HBITMAP oldBmp = (HBITMAP)::SelectObject(tempDC, newBMP);
BitBlt(tempDC,0,0,cx,cy, winDC,0,0,SRCCOPY|CAPTUREBLT);
// now you have the window content in the newBMP bitmap, do with it as you please here
::SelectObject(tempDC, oldBmp);
::DeleteObject(newBMP);
::DeleteDC(tempDC);
::ReleaseDC(hWnd, winDC);