Switch to full screen with Direct3D12
While listening to Dr. ChatGPT, I implemented switching between full screen mode and windowed mode in Direct3D12. The idea is to switch between the two by pressing the F11 key.
SampleApp g_app;
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
// ImGui Window Processing
if (ImGui_ImplWin32_WndProcHandler(hWnd, message, wParam, lParam)) {
return true;
}
static bool isFullscreen = false;
static bool isFullscreenInActive = false;
static WINDOWPLACEMENT windowPlacement = { sizeof(WINDOWPLACEMENT) };// save position and size in window mode
switch (message) {
case WM_DESTROY:
g_app.SetFullscreenState(FALSE);
PostQuitMessage(0);
return 0;
case WM_KEYDOWN:
if (wParam == VK_F11) {// Switch with F11 key
if (isFullscreen) {
// Return to window mode
g_app.SetFullscreenState(FALSE);
SetWindowLong(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
SetWindowPlacement(hWnd, &windowPlacement);// Restore previous window position and size
SetWindowPos(hWnd, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
isFullscreen = false;
}
else {
// Put the window in full screen mode
GetWindowPlacement(hWnd, &windowPlacement);// Save current window position and size
SetWindowLong(hWnd, GWL_STYLE, WS_POPUP | WS_VISIBLE);// Remove window border and title bar
SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
//SetWindowPos(hWnd, HWND_TOP, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), SWP_FRAMECHANGED);
g_app.SetFullscreenState(TRUE);
isFullscreen = true;
}
}
return 0;
case WM_SIZE:
if (wParam != SIZE_MINIMIZED) {
UINT width = LOWORD(lParam);
UINT height = HIWORD(lParam);
g_app.SetScreenSize(width, height);
}
break;
case WM_ACTIVATE:
if (wParam == WA_INACTIVE) {
// When a window is deactivated
if (isFullscreen) {
// Return to windowed mode from full screen mode
g_app.SetFullscreenState(FALSE);
SetWindowLong(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
SetWindowPlacement(hWnd, &windowPlacement);// Restore previous position and size
SetWindowPos(hWnd, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
isFullscreenInActive = true;
}
}
else if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE) {
// If the window becomes active again
if (isFullscreenInActive) {
// Return to full screen if necessary
SetWindowLong(hWnd, GWL_STYLE, WS_POPUP | WS_VISIBLE);
SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
g_app.SetFullscreenState(TRUE);
isFullscreenInActive = false;
}
}
return 0;
default:
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
g_app.SetFullscreenState()is defined as follows
bool SetFullscreenState(BOOL is_fullscreen)
{
fullScreen = (is_fullscreen == TRUE);
if (swapChain)
{
// Command queue flushing process
WaitForFence();
// Full screen setup
swapChain->SetFullscreenState(is_fullscreen, nullptr);
return true;
}
return false;
}
I am not sure if WM_ACTIVATE processing is necessary, but I am writing a process to temporarily return to windowed mode when an app is no longer active in full screen mode, such as Alt+Tab .
The stuck point is this part.
SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
//SetWindowPos(hWnd, HWND_TOP, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), SWP_FRAMECHANGED);
I tried to set the resolution of the monitor when going to full screen, but I was correct in not setting the size. You do not have to set it here, The resizing process is automatically called at the timing of SetFullscreenState.
In addition, SetScreenSize is the following process.
void SetScreenSize(int width, int height)
{
viewport.Width = static_cast<float>(width);
viewport.Height = static_cast<float>(height);
scissorRect.right = width;
scissorRect.bottom = height;
if (swapChain) {
// Command queue flushing process
WaitForFence();
// Resize back buffer
for (UINT i = 0; i < FRAME_BUFFER_COUNT; ++i) {
renderTargets[i].Reset();
}
// Resize swap chain buffer
swapChain->ResizeBuffers(FRAME_BUFFER_COUNT, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, 0);
// Re-create descriptor heap
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
rtvHeapDesc.NumDescriptors = FRAME_BUFFER_COUNT;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&rtvHeap));
// Re-create RTV for new back buffer
UINT rtvDescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = rtvHeap->GetCPUDescriptorHandleForHeapStart();
for (UINT i = 0; i < FRAME_BUFFER_COUNT; ++i) {
swapChain->GetBuffer(i, IID_PPV_ARGS(&renderTargets[i]));
device->CreateRenderTargetView(renderTargets[i].Get(), nullptr, rtvHandle);
rtvHandle.ptr += rtvDescriptorSize;
}
}
}