Clearing the screen after switching away from fullscreen mode? - c++

So I've got an OpenGL application running that can toggle between fullscreen mode and windowed mode. It currently does this by resizing the window and changing styles.
However, it seems to not invalidate the screen when switching from fullscreen mode to windowed mode, which leaves things I've drawn lingering onscreen after the switch.
Interestingly, it only exhibits this behavior in single monitor mode. If I'm running with multiple monitors, it invalidates okay, and clears my drawing.
I don't think this is a driver bug, as it's happening on two separate computers using two separate video cards(although admittedly they are both nVidia.), I'm thinking I'm doing something wrong somewhere.
I've tried a bunch of methods for getting Windows to clear the screen of my previously fullscreen drawings, but nothing seems to work. InvalidateRect(), RedrawWindow(), ChangeDisplaySettings()...
Specifically:
InvalidateRect(m_hwnd, &rectx, true); // rect being the dimensions of either the screen or my window.
InvalidateRect(HWND_DESKTOP, NULL, TRUE); // Doesn't seem to do anything.
RedrawWindow(NULL, NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW);
ChangeDisplaySettings(NULL, 0);
Well, actually, one thing that does seem to work is ShowWindow(hwnd, SW_HIDE) before resizing. However that loses focus for a moment, allowing other applications to grab my application's input, and seems a bad way to go about it.
I should note that I'm not doing any actual display mode changes when I'm seeing this behavior; just staying at the current resolution for fullscreen.
I'm a bit clueless where I'm going wrong. Simplified code:
if(m_isFullscreen)
{
ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN);
}
else
{
ChangeDisplaySettings(&m_dmSavedScreenSettings, 0);
}
if(m_isFullscreen)
{
dwExStyle = WS_EX_APPWINDOW;
dwStyle = WS_POPUP;
ShowCursor(false);
}
else
{
dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
dwStyle = WS_OVERLAPPEDWINDOW;
if(m_isRunning) // Because ShowCursor uses a counter to indicate, and windowed mode defaults to cursor on, we don't need to increment the counter and double it being on.
{
ShowCursor(true);
}
}
RECT rect;
rect.left = 0;
rect.top = 0;
if(m_isFullscreen) { rect.right = 1280; } else { rect.right = 640; }
if(m_isFullscreen) { rect.bottom = 1024; } else { rect.bottom = 480; }
AdjustWindowRectEx(&rect, dwStyle, false, dwExStyle);
SetWindowLongPtr(m_hwnd, GWL_STYLE, dwStyle | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
SetWindowLongPtr(m_hwnd, GWL_EXSTYLE, dwExStyle);
if(m_isFullscreen)
{
MoveWindow(m_hwnd, 0, 0, 1280, 1024, true);
}
else
{
MoveWindow(m_hwnd, 0, 0, 640, 480, true); // windowed
}
And that's more or less it. Some other supporting code and error checking, but that's what I'm doing... dmSavedSettings is saved before m_hwnd is assigned from NULL, and not afterwards. My initial window creation works fine, and fullscreen works fine. It's just returning to Windowed after being fullscreen that's the issue.

If you set a null background brush in your window class, windows will not be cleared automatically. You must add a WM_PAINT handler that calls your OpenGL display handler, which in turn clears the viewport (glClearColor) and redraws.

As datenwolf mentions in another answer's comment, you want to use SetWindowPos() instead of MoveWindow() when making use of SetWindowLongPtr().
My dirty background problems were solved by calling ChangeDisplaySettings(NULL, 0) AFTER resizing my window. Doing it before does little, but afterwards appears to work fine.

Related

Mouse input not being released from other process's window

I am writing a C++ Windows program that displays game stats/friends info over games using a Win32 window and a DirectX11 renderer. (that renders a UI that is controlled with the mouse and keyboard)
The window is overlaid on top of the game’s window and has the flags WS_EX_TRANSPARENT and WS_POPUP set.
When the window is activated, I set WS_EX_LAYERED to capture inputs.
The created window is positioned on top of the target window if GetWindow(target_, GW_HWNDPREV) is different from the handle of the created window.
It is placed on top of it by calling SetWindowPos with SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_ASYNCWINDOWPOS.
I’ve double-checked that the flags are set correctly and that the functions are being called.
I also tried using ShowWindow with the SW_SHOW flag, but the result remained unchanged.
I’m currently running my tests on Portal 2, but ideally, I would want this to work on the majority of games. (OS used is Windows 11 22H2)
To activate the window and release the mouse capture from the game, I am calling SetForegroundWindow, SetActiveWindow, and SetFocus, all with the HWND of my window.
This approach works correctly when I run the program from Visual Studio, but when I run the compiled executable, the mouse remains locked in the game.
Both builds were tested in debug and release mode, and I really can't figure out why this is happening.
LRESULT Renderer::WndProc(...) {
switch (message) {
case WM_SIZE:
// resize buffers and recreate render target view
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(handle, message, w_param, l_param);
}
bool Window::Create(...) {
// ...
hwnd_ = CreateWindowEx(
wndclass,
class_name_.c_str(),
title_.c_str(),
WS_POPUP,
0, 0, 10, 10,
nullptr,
nullptr,
nullptr,
nullptr
);
SetLayeredWindowAttributes(hwnd_, 0, 255, LWA_ALPHA);
UpdateWindow(hwnd_);
constexpr MARGINS margin = {-1, -1, -1, -1};
const auto result = DwmExtendFrameIntoClientArea(hwnd_, &margin);
// ...
}
void Window::Activate() {
// Remove the WS_EX_LAYERED attribute.
SetClickThrough(false);
SetForegroundWindow(hwnd_);
SetActiveWindow(hwnd_);
SetFocus(hwnd_);
}
// ----------------------
// Sample main routine pseudocode:
// ----------------------
renderer->window.Create(...);
while (renderer->is_running()) {
renderer->BeginFrame();
// Position the window on top of the game found.
renderer->window().FollowTarget();
// Toggle the visibility using the F2 key.
// If transitioning from hidden to visible, call the window
// activation routine.
if (utils::KeyPressed(VK_F2)) {
if (ui->is_visible()) {
// .. window deactivation not included
ui->set_visible(false);
}
else {
renderer->window().Activate();
ui->set_visible(true);
}
}
ui->Draw(renderer);
renderer->Present();
}
I considered using a low-level keyboard/mouse hook to capture inputs, or offscreen rendering and presenting it in the game using a DirectX hook, but I’d rather avoid it as it would require many games to manually whitelist it.
Is there something else I’m missing or a different approach I should be taking?

SetWindowPos centers window when position is adjacent to working area border

I'm trying to place dialog window adjacent to desktop's top or bottom border.
Code:
CRect MonitorWorkingArea(HMONITOR monitor)
{
MONITORINFOEX monInfo;
zeroVar(monInfo);
monInfo.cbSize = sizeof(monInfo);
GetMonitorInfo(monitor, &monInfo);
return monInfo.rcWork;
}
CRect MonitorWorkingArea_Wnd(HWND hwnd)
{
return MonitorWorkingArea(MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST));
}
CRect MonitorWorkArea_Wnd(CWnd& wnd)
{
return MonitorWorkingArea_Wnd(wnd.GetSafeHwnd());
}
void CMyDialog::SetDefaultWndPos(bool toBottom)
{
// Here we can be sure, that
// a) parent of this window is not null, is visible, not minimized
// b) this window smaller than working area of desktop
// c) it has WS_POPUP style
CRect rcThis;
GetWindowRect(&rcThis);
// Shift our rectangle to most upper or most bottom position
const CRect rcDesktop = MonitorWorkingArea_Wnd(*this);
rcThis.MoveToY(toBottom ? rcDesktop.bottom - rcThis.Height() : 0);
// Move window
SetWindowPos(NULL, rcThis.left, rcThis.top, 0, 0,
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
}
Real code is bit more complex, I omit some checks and features for simplicity.
I have two monitors, both are 1920×1080 and in virtual coordinate space they are laying on:
Primary: (0,0) — (1920,1080)
Secondary: (1920,0) — (3840, 1080)
Code above works fine when window placed on primary monitor, but on secondary one it's works unexpectedly: it centers window based on it's parent.
For instance I'm calling SetDefaultWndPos(false) and rcThis being calculated as {left:2710, top:0, right:3423, bottom:550}. So SetWindowPos is called with zero y:
SetWindowPos(NULL, 2710, 0, 0, 0,
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
But in handler of WM_WINDOWPOSCHANGING
void CMyDialog::OnWindowPosChanging(WINDOWPOS* lpwndpos)
{
__super::OnWindowPosChanging(lpwndpos);
}
I see in debug x = 2710 (unchanged) and y = 475 (unexpected). Next I check dialog's y-position in Spy utility, it's 475. Dialog window looks like it was centered on it's parent window.
All it looks like an OS errorneously detected requested window position as out-of-desktop and used some default positioning.
Only workarund I found is to decrease desktop working area on 1 pixel from each side:
CRect MonitorWorkingArea(HMONITOR monitor)
{
MONITORINFOEX monInfo;
zeroVar(monInfo);
monInfo.cbSize = sizeof(monInfo);
GetMonitorInfo(monitor, &monInfo);
CRect rc = monInfo.rcWork;
if( !(monInfo.dwFlags & MONITORINFOF_PRIMARY) )
{
++rc.left;
++rc.top;
--rc.right;
--rc.bottom;
}
return rc;
}
It works but looks as strange ugly hack.
Can anybody explain what happens here and help me with good decision?
Can it be OS version dependent? (I use Windows 7 Professional SP1).

CreateWindowEx posts WM_SIZE?

CreateWindowEx API really posts WM_SIZE message?
When I create a window via CreateWindowEx as full screen mode,
CreateWindowEx posts WM_SIZE but window mode doesn't.
My code sets the window style like this :
if(bFullscr)
{
//When the window is in full screen mode.
nStyle = WS_POPUP;
nExtraStyle = WS_EX_APPWINDOW;
}
else
{
//Otherwise.
nStyle = WS_OVERLAPPEDWINDOW;
nExtraStyle = (WS_EX_APPWINDOW | WS_EX_WINDOWEDGE);
}
And changes display settings like this (full screen mode only) :
if(bFullscr)
{
DEVMODE sScrSet;
memset(&sScrSet, 0, sizeof(DEVMODE));
sScrSet.dmSize = sizeof(DEVMODE);
sScrSet.dmPelsWidth = nWidth;
sScrSet.dmPelsHeight = nHeight;
sScrSet.dmBitsPerPel = nColorBit;
sScrSet.dmFields = (DM_BITSPERPEL | DM_PELSHEIGHT | DM_PELSWIDTH);
if(ChangeDisplaySettings(&sScrSet, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
{
//Error routine.
}
}
I'm really wonder why CreateWindowEx posts WM_SIZE message selectively.
If you simply want to resize the window, somewhere in your code you should have ShowWindow(hWnd, nCmdShow); change it as follows:
ShowWindow(hWnd, SW_SHOWDEFAULT);//show normal
ShowWindow(hWnd, SW_SHOWMAXIMIZED);//show maximized (full screen)
SetWindowPos(hWnd, NULL, 10, 10, 300, 300, SWP_SHOWWINDOW);//show at specific position
Also you could use WS_MAXIMIZE in CreateWindow, but that could complicate things. Window usually has WS_OVERLAPPEDWINDOW or WS_POPUP|WS_CAPTION|WS_SYSMENU. You should pick one and keep it simple.
When Window size changes, it receives WM_SIZE, you can catch that and examine it.

window with transparent client area

I register the window class like this:
WNDCLASSEX wctt;
wctt.cbSize = sizeof(WNDCLASSEX);
wctt.style = CS_DBLCLKS;
wctt.lpfnWndProc = WndProcTooltip;
wctt.cbClsExtra = 0;
wctt.cbWndExtra = 0;
wctt.hInstance = m_hAppInstance;
wctt.hIcon = NULL;
wctt.hCursor = LoadCursor(NULL, IDC_SIZE);
wctt.hbrBackground = NULL;
wctt.lpszMenuName = NULL;
wctt.lpszClassName = _T("myWindow");
wctt.hIconSm = NULL;
RegisterClassEx(&wctt)
As you can see I use wctt.hbrBackground = NULL; so it will have no background.
The window is created like this:
::CreateWindowEx(WS_EX_TOPMOST | WS_EX_TOOLWINDOW,
_T("myWindow"),
NULL,
WS_VISIBLE | WS_POPUP,
50,
50,
150,
100,
NULL,
NULL,
m_hAppInstance,
NULL);
In the paint section I draw icon on the window:
PAINTSTRUCT ps;
HDC hdc;
BITMAP bitmap;
ICONINFO iconinfo;
hdc = ::BeginPaint(hWnd, &ps);
::SetBkMode(hdc,TRANSPARENT);
::GetIconInfo(localIcon, &iconinfo);
::GetObject(iconinfo.hbmColor, sizeof(bitmap), &bitmap);
::DeleteObject(iconinfo.hbmColor);
::DeleteObject(iconinfo.hbmMask);
::DrawIconEx(hdc, 0,0, localIcon, bitmap.bmWidth, bitmap.bmHeight, 0, NULL, DI_NORMAL);
The icon size is smaller than the window size and I get on the background the current view on the window below the popup.
But now when I move the window (or minimize the window below the popup) the background is not changing.
I was trying to make a timer that each time do the flowing:
RECT rcClient;
GetClientRect(hWnd, &rcClient);
InvalidateRect(hWnd,&rcClient,TRUE);
This makes the print function run again but the background of the icon is not changing.
Should I do anything in WM_ERASEBKGND?
Does Anyone have any idea how to make it work?
thanks,
guy
It's not enough to just let the background stay unpainted; you also need to get the window below yours to repaint itself when necessary.
If the windows are part of the same hierarchy, created by the same thread, it is sufficient to give your window the WS_EX_TRANSPARENT extended style. This causes the window underneath to paint itself first so the background is always up-to-date.
Otherwise you need to use SetWindowRgn so that your window actually doesn't exist outside of the borders you wish to paint.
Look at Layered Window. This feature allows creating semi-transparent windows of different shapes.
Add WS_EX_LAYERED extended attribute in your window class.
You can control the transparency of your window with these two functions:
SetLayeredWindowAttributes:
bAlpha controls the opacity of the entire window, if you pass LWA_ALPHA in dwFlags.
When bAlpha is 0, the window is completely transparent. When bAlpha is 255, the window is opaque.
crKey sets the color that would transparent.
All pixels painted by the window in this color will be transparent.
UpdateLayeredWindow gives you precise control over window transparency, you can give different parts of window different levels of transparency.
If you're trying to create a non-rectangular window, this is not sufficient. Setting "no background" simply means the background will not be drawn, and you'll see whatever happens to be in memory at that location.
To create a non-rectangular window, have a look at the SetWindowRgn function.

Is there a graceful way to handle toggling between fullscreen and windowed mode in a Windows OpenGL application?

I'm wondering if it's possible to toggle back and forth between fullscreen mode and windowed mode in an OpenGL window(I'm writing for Windows using C++ and win32), without destroying the OpenGL context, and thus having to reload assets(Textures, VBOs, etc) in the process?
This is undesirable because it introduces a delay in switching between fullscreen and windowed mode, potentially a long one, as well as making it easier to screw things up by forgetting to reinitialize something.
As a followup to that, are there certain visual effects that are broken by managing to do this?
I've done a fair bit of searching and reading for the past few days, and despite a lot of flaming of SDL and other frameworks for having the same problem(I'm not using them anyway, but...), the best I've managed to find is a possible lead on opening a 1x1 window in the background to retain the context while a secondary window is destroyed or created at whim. And that's seeming unreliable from the comments I found regarding it, and seems very kludgey regardless.
Is there a proper way to do this, or is the proper way the often-given-as-an-example method of destroying your window, and recreating it, including destroying your OpenGL context and recreating it?
Basically it's just resizing the window and specifying flags that the border is invisible.
SetWindowLongPtr(hWnd, GWL_STYLE,
WS_SYSMENU | WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE);
MoveWindow(hWnd, 0, 0, width, height, TRUE);
to set it back:
RECT rect;
rect.left = 0;
rect.top = 0;
rect.right = width;
rect.bottom = height;
SetWindowLongPtr(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE);
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE);
MoveWindow(hWnd, 0, 0, rect.right-rect.left, rect.bottom-rect.top, TRUE);
or for a not-resizable window:
SetWindowLongPtr(hWnd, GWL_STYLE, WS_CAPTION | WS_POPUPWINDOW | WS_VISIBLE);
AdjustWindowRect(&rect, WS_CAPTION | WS_POPUPWINDOW, FALSE);
MoveWindow(hWnd, 0, 0, rect.right-rect.left, rect.bottom-rect.top, TRUE);
and then just resize your OpenGL viewport settings.
If you want to set the display mode too, use this:
// change display mode if destination mode is fullscreen
if (fullscreen) {
DEVMODE dm;
dm.dmSize = sizeof(DEVMODE);
dm.dmPelsWidth = width;
dm.dmPelsHeight = height;
dm.dmBitsPerPel = bitsPerPixel;
dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;
success = ChangeDisplaySettings(&dm, 0) == DISP_CHANGE_SUCCESSFUL;
}
// reset display mode if destination mode is windowed
if (!fullscreen)
success = ChangeDisplaySettings(0, 0) == DISP_CHANGE_SUCCESSFUL;
Here's the code I use, which uses SetWindowPos() rather than MoveWindow(), as discussed in the comments of the other answer.
void enter_fullscreen(application* App)
{
POINT Point = {0};
HMONITOR Monitor = MonitorFromPoint(Point, MONITOR_DEFAULTTONEAREST);
MONITORINFO MonitorInfo = { sizeof(MonitorInfo) };
if (GetMonitorInfo(Monitor, &MonitorInfo)) {
DWORD Style = WS_POPUP | WS_VISIBLE;
SetWindowLongPtr(App->Window, GWL_STYLE, Style);
SetWindowPos(App->Window, 0, MonitorInfo.rcMonitor.left, MonitorInfo.rcMonitor.top,
MonitorInfo.rcMonitor.right - MonitorInfo.rcMonitor.left, MonitorInfo.rcMonitor.bottom - MonitorInfo.rcMonitor.top,
SWP_FRAMECHANGED | SWP_SHOWWINDOW);
}
App->IsFullscreen = true;
}
void exit_fullscreen(application* App)
{
bool WasMaximized = App->IsMaximized;
DWORD Style = WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN;
if (WasMaximized) {
Style = Style | WS_MAXIMIZE;
}
ivec2 WindowPosition = WasMaximized ? App->WindowPosition : App->NormalWindowPosition;
ivec2 WindowSize = WasMaximized ? App->WindowSize : App->NormalWindowSize;
SetWindowLongPtr(App->Window, GWL_STYLE, Style);
SetWindowPos(App->Window, 0,
WindowPosition.X, WindowPosition.Y, WindowSize.X, WindowSize.Y,
SWP_FRAMECHANGED | SWP_SHOWWINDOW);
App->IsFullscreen = false;
}
I call it on F11, but also on WM_ACTIVATE. Otherwise the window would sometimes keep rendering on top on Windows 7, even if another application would receive all messages, including mouse and keyboard.