On day4 we have the animated weird gradient. To do this we have a backbuffer to which we write the pixels of the gradient using the RenderWeirdGradient
function. And we put those pixels onto the screen using the StretchDIBits
function by giving it a HDC
.
However, this StretchDIBits
function is called twice (through Win32UpdateWindow
) for each iteration of the main loop we run. Once in the loop itself:
while(Running) { // -----snip----- HDC DeviceContext = GetDC(Window); // ----snip------ Win32UpdateWindow(DeviceContext, &ClientRect, 0, 0, WindowWidth, WindowHeight); // ----snip----- }
and once through the handler for WM_PAINT
:
case WM_PAINT: { HDC DeviceContext = BeginPaint(Window, &Paint); //-----snip---- Win32UpdateWindow(DeviceContext, &ClientRect, X, Y, Width, Height); //-----snip---- } break;
Since, it is getting called in the WM_PAINT
I tried removing it from the mainloop. And this led to nothing getting painted on the screen because no WM_PAINT
messages were coming unless I moved the window around.
So, then I tried removing the handler for WM_PAINT
. And this got me the gradient back. So, it appears that just calling the StretchDIBits
is somehow making windows send the WM_PAINT
messsage and the only thing we need to do inside the WM_PAINT
handler is call BeginPaint
and EndPaint
(I tried this and it works) or just leave it to the default handler.
So are we calling the StretchDIBits
function in the mainloop just to trigger the WM_PAINT
?
I also tried to trigger WM_PAINT
by other means:
Method: 1
UpdateWindow(Window); /* HDC DeviceContext = GetDC(Window); RECT ClientRect; GetClientRect(Window, &ClientRect); int WindowWidth = ClientRect.right - ClientRect.left; int WindowHeight = ClientRect.bottom - ClientRect.top; Win32UpdateWindow(DeviceContext, &ClientRect); ReleaseDC(Window, DeviceContext); */
Method: 2
/* HDC DeviceContext = GetDC(Window); RECT ClientRect; GetClientRect(Window, &ClientRect); int WindowWidth = ClientRect.right - ClientRect.left; int WindowHeight = ClientRect.bottom - ClientRect.top; Win32UpdateWindow(DeviceContext, &ClientRect); ReleaseDC(Window, DeviceContext); */ RECT ClientRect; GetClientRect(Window, &ClientRect); InvalidateRect( Window, &ClientRect, false );
I found that UpdateWindow
does not do the job while InvalidateRect
does it with the last parameter set to either true
or false
. So based on this, I feel we are doing a wasteful copy of the buffer into windows's memory? And why does the UpdateWindow
not work? The docs say that UpdateWindow
will send WM_PAINT
to the window directly but my breakpoint on WM_PAINT
never trips even though UpdateWindow
is tripping with each iteration.
The UpdateWindow function updates the client area of the specified window by sending a WM_PAINT message to the window if the window's update region is not empty. UpdateWindow on MSDN
If you do not want the application to wait until the application's message queue has no other messages, use the UpdateWindow function to force the WM_PAINT message to be sent immediately. If there is any invalid part of the client area, UpdateWindow sends the WM_PAINT message for the specified window directly to the window procedure. Invalidating the Client Area on MSDN
UpdateWindow
will call the window procedure with a WM_PAINT message without going through GetMessage
or PeekMessage
, ONLY if there is an update region (you called InvalidateRect
or InvalidateRng
).
Since UpdateWindow
doesn't go through the message queue, you need to have the drawing code in the WM_PAINT message if you want to use that function. I'm not very familiar with WM_PAINT but maybe there are other way to get those messages without going through the message queue which would be a reason to keep the drawing code there. In application that use D3D, OpenGL or other graphics API, I don't think you ever need to handle WM_PAINT
messages.
There might be more information here https://learn.microsoft.com/en-us/windows/win32/gdi/painting-and-drawing
The main reason to have drawing in WM_PAINT is because that is only code executed when you are moving window or when you are resizing it. During these operations your main loop does not run as message processing loop enters separate modal loop.
If you don't care about redrawing in such case, then there's no need to use WM_PAINT and just having drawing in main loop is enough.
But if you want to handle such drawing, then having draw code in WM_PAINT and main loop is fine to have. It does not mean it will be always called twice. Simply in normal game run the main loop will do the drawing. And only when window is moved/resized/obscured then WM_PAINT draw kicks-in. You can issue InvalidateRect to force WM_PAINT from main code. But meh.. it does not really matter.
I see, that explains why I wasn't getting the WM_PAINT
message when I was calling just UpdateWindow
Hmm I see yeah I don't know why I would want to paint when its moving...