Direct3D12でフルスクリーンに切り替える
ChatGPT先生に聞きながら、Direct3D12でフルスクリーンモードとウィンドウモードの切り替えを実装しました。 F11キーを押すことで切り替えるというものです。
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のウィンドウ処理
if (ImGui_ImplWin32_WndProcHandler(hWnd, message, wParam, lParam)) {
return true;
}
static bool isFullscreen = false;
static bool isFullscreenInActive = false;
static WINDOWPLACEMENT windowPlacement = { sizeof(WINDOWPLACEMENT) }; // ウィンドウモード時の位置とサイズを保存
switch (message) {
case WM_DESTROY:
g_app.SetFullscreenState(FALSE);
PostQuitMessage(0);
return 0;
case WM_KEYDOWN:
if (wParam == VK_F11) { // F11キーで切り替え
if (isFullscreen) {
// ウィンドウモードに戻す
g_app.SetFullscreenState(FALSE);
SetWindowLong(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
SetWindowPlacement(hWnd, &windowPlacement); // 以前のウィンドウの位置とサイズを復元
SetWindowPos(hWnd, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
isFullscreen = false;
}
else {
// フルスクリーンモードにする
GetWindowPlacement(hWnd, &windowPlacement); // 現在のウィンドウの位置とサイズを保存
SetWindowLong(hWnd, GWL_STYLE, WS_POPUP | WS_VISIBLE); // ウィンドウの境界線とタイトルバーを削除
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) {
// ウィンドウが非アクティブになった場合
if (isFullscreen) {
// フルスクリーンモードからウィンドウモードに戻す
g_app.SetFullscreenState(FALSE);
SetWindowLong(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
SetWindowPlacement(hWnd, &windowPlacement); // 以前の位置とサイズを復元
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 (isFullscreenInActive) {
// 必要ならフルスクリーンに戻す
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()は以下のような定義です。
bool SetFullscreenState(BOOL is_fullscreen)
{
fullScreen = (is_fullscreen == TRUE);
if (swapChain)
{
// コマンドキューのフラッシュ処理
WaitForFence();
// フルスクリーンの設定
swapChain->SetFullscreenState(is_fullscreen, nullptr);
return true;
}
return false;
}
WM_ACTIVATEの処理は必要なのかよくわかりませんが、 Alt+Tab などでフルスクリーン状態のアプリがアクティブでなくなったとき、一時的にウィンドウモードに戻すという処理を書いています。
はまった点は、この部分です。
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);
フルスクリーンにするときに、モニターの解像度を設定しようとしたのですが、サイズは設定しないというのが正解でした。ここで設定しなくても、 SetFullscreenStateのタイミングでサイズ変更の処理が自動的に呼ばれます。
ついでに、SetScreenSizeは、以下のような処理です。
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) {
// コマンドキューのフラッシュ処理
WaitForFence();
// バックバッファをリサイズ
for (UINT i = 0; i < FRAME_BUFFER_COUNT; ++i) {
renderTargets[i].Reset();
}
// スワップチェーンのバッファをリサイズ
swapChain->ResizeBuffers(FRAME_BUFFER_COUNT, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, 0);
// ディスクリプタヒープの再作成
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));
// 新しいバックバッファに対するRTVの再作成
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;
}
}
}