I was looking into why when running my Windows App, it has a short flash of white background before rendering the actual app (i.e., before WM_ERASEBKGND and WM_PAINT is received).
Now, I just noticed that this problem is also present in the default sample app created by Visual Studio. At least this is the case for me when running under Windows 10,21H1 (in VS2008 & VS2013).
The only thing you have to do, after creating a "new Win32 Project", is change the background color of the window class:
//wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW 1);
wcex.hbrBackground = (HBRUSH) CreateSolidBrush(RGB(255, 0, 0));
e.g., to the color red.
And then add a WM_ERASEBKGND with a Sleep to the WndProc:
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
EndPaint(hWnd, &ps);
break;
case WM_ERASEBKGND:
Sleep(1000);
return DefWindowProc(hWnd, message, wParam, lParam);
The Sleep exaggerates the problem, causing the white background to show for at least a second. After that the red background is drawn as expected.
This means that Windows does indeed know and respect hbrBackground, which is great! For some odd reason it just doesn't clear it to that, but to white instead.
(Incidentally, I went through all the system colors in the registry (HKEY_CURRENT_USER\Control Panel\Colors HKEY_CURRENT_USER\Control Panel\Desktop\Colors) with a "255 255 255" setting and forcibly changed them to see if that would change the initial white background. But no luck. This makes me conclude that the white background is not a system color.)
Anyway, the above lead me to try to programmatically resize the window after ShowWindow. But since I don't want it to flicker on open, do the ShowWindow off-screen.
So here is the code that would replace regular ShowWindow(..):
int x0 = GetSystemMetrics(SM_XVIRTUALSCREEN);
int x1 = GetSystemMetrics(SM_CXVIRTUALSCREEN);
RECT rect;
GetWindowRect(hWnd, &rect);
// resize and move off-screen
SetWindowPos(hWnd, NULL, x1-x0, 0, 0, 0, SWP_NOREDRAW );
// show window
ShowWindow(hWnd,nCmdShow);
// restore and redraw
SetWindowPos(hWnd, NULL, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, 0 );
Now, I would call this a hack. Yet, it does not rely on WM_ERASEBKGND nor WM_PAINT, so there should be less of a timing issue. Also, the window shows up exactly like a regular ShowWindow(...) would, just with the correct hbrBackground, which is what I wanted.
Here is what it looks like @ 25Hz:
Notice that there is no flash of white background.
Please note that I've tried to write the code to cater for virtual desktop/multi-monitor, but haven't actually tested that.
But unfortunately everything is not fine and dandy. As I was writing this answer, I did quite a few trial runs with OBSStudio recording @ 60Hz, and went through the footage. There I found one that simply shows trash inside the window frame on open (apparently from Chrome), for just one frame. Here is a slowed-down replay:
I'm stumped. Perhaps that is the real issue ?
CodePudding user response:
I did some more testing, and want to post a potential answer to this question. Now, this is mainly based on the suggestion by @JonathanPotter, so full credit to him. And while it doesn't really fix the problem, it does alleviate it quite a bit.
Now, ideally, it would be great if Windows would simply render the window with the correct initial background color, but no matter how hard I've tried, I can only get it to update the background color by utilizing WM_ERASEBKGND or WM_PAINT.
So it seems that the time delay between showing the window (i.e. using ShowWindow), and the actual clearing of the background (WM_ERASEBKGND) is the crux of the problem. Hence, it makes sense to profile it. I've done so by recording the time difference between calling ShowWindow and reaching WM_ERASEBKGND using QueryPerformanceCounter.
So on an i7-4960HQ CPU @ 2.60GHz running Window 10, the time between ShowWindow and WM_ERASEBKGND is between 100 - 317ms. It fluctuates quite a bit. This is with a vanilla Win32 Sample App, built in Release without any Sleeps or anything like that, but using a red hbrBackground to show the issue. This means that the white background is clearly visible for a few frames before the red background is drawn. Here is an animated gif captured @ 25Hz: The white background is visible for 3 frames in that animation.
Now the potential fix is to use a combination of SetWindowPos and RedrawWindow before showing the window.
For my tests I simply added these two lines before calling ShowWindow(..):
SetWindowPos(hWnd, NULL, 0,0,0,0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW);
RedrawWindow(hWnd, NULL, 0, RDW_INVALIDATE | RDW_ERASE);
Although RedrawWindow does not seem to make any difference. Profiling again, the time between ShowWindow and WM_ERASEBKGND is now 10 - 23ms. A 10x speed-up!
Again, an animated gif captured (with SetWindowPos) @ 25Hz: This clearly shows that the flash of white background is gone, and thus the problem is fixed. It's like night and day.
Now, I would argue that this is not a fix, but rather a workaround. Since the underlying problem of Windows using a white background color is still there. And since this is a timing issue, I can easily imagine that the white background could show up again, say if the system was sluggish or busy doing other stuff. Similarly, having a faster system means you that you are less likely to see this in the first place, effectively hiding the issue. But simply setting a breakpoint in WM_ERASEBKGND will still show you a white window.
Also, I have no explanation for the speed-up. I tracked the number of messages in the message pump, and they are the same in both scenarios.
Now, I'm still hoping for a better fix. I find it hard to believe that the Microsoft engineers found it cool to fill all freshly created Windows with a hardcoded 0xFFFFFF, so I'm hoping that this color is actually read from somewhere, and thus possible to change, so the initial background matches the hbrBackground.
Please feel free to post alternative answers, questions, or suggestions. I will of course update this thread if I figure out anything else.