Saturday, June 28, 2014

Enabling Direct3D fullscreen mode from non-active window

In this post I am going to tell you how can you enable Direct3D fullscreen mode from non-active window which is used as swap chain's rendering target.

Initially I didn't realize it could be challenging to enable Direct3D fullscreen mode from non-active window. I thought all I need to do is call the SetFullscreenState method of the SwapChain which is also worked fine from test application. But when I tried the same thing from the real WinForm application which was not active/foreground window at the time of enabling fullscreen, it didn't work. I was getting DXGI_ERROR_NOT_CURRENTLY_AVAILABLE error from SetFullscreenState method.

When I checked MSDN page about the SetFullscreenState method I can clearly see why it is failing.
DXGI_ERROR_NOT_CURRENTLY_AVAILABLE if the action failed. There are many reasons why a windowed-mode swap chain cannot switch to full-screen mode. For instance:
  • The application is running over Terminal Server.
  • The output window is occluded.
  • The output window does not have keyboard focus.
  • Another application is already in full-screen mode.
The third point mentioned in the MSDN page is clearly my situation. So I thought I can simply set the keyboard focus to my WinForm application window using SetForegroundWindow Win32 API. But it seems it is not as simple as it sounds, Windows impose many restrictions on SetForegroundWindow API which are explained in detail in the MSDN page. Basically a non-foreground window can't set itself or another window as foreground window. But a foreground window can set another window as foreground window.

Apparently there are ways/hacks in which we can overcome this restriction and they are explained here, here and here. I tried the first solution mentioned in the post here and it worked fine. The solution is wrapped inside the following function, the hwnd is the handle of target window which want it to be foreground/active window.

void ActivateWindow(HWND hwnd)
    DWORD foregroundThread = ::GetWindowThreadProcessId(GetForegroundWindow(), NULL);
    DWORD thisThread = ::GetCurrentThreadId();

    if (foregroundThread != thisThread)
        BOOL isAttached = ::AttachThreadInput(foregroundThread, thisThread, TRUE);

        ::ShowWindow(hwnd, SW_SHOW);

        if (isAttached)
            ::AttachThreadInput(foregroundThread, thisThread, FALSE);

The hack involves temporarily attaching our thread as the active foreground window's input processing thread and trying to bring the our window to the top most. This works as we fake our window as the active foreground window by hooking its input processing mechanism and Windows thinks we are the active foreground window and wants to change the focus. After bring our application to the top most we revert the input processing mechanism to its original thread as our goal is achieved. Once the window becomes active window then full screen mode enabled without any problem.

I hope the above description of my experience and the code snippet helps you, cheers.

No comments: