レンダリングを行う機能と、レンダリング結果(フレームバッファの内容)を画面に表示する機能は別に分かれている。前者は DirectX と呼ばれ、後者は DXGI (DirectX Graphic Infrastructure) と呼ばれる仕組みが担ってる。
このような機能の分割は、他のグラフィックス API でも同様で、例えば OpenGL であれば、ネイティブの描画部分は EGL という仕組みで独立しており、Vulkan であれば WSI という仕組みが提供されている。
DXGIの呼び出し方
DXGI は DirectX よりも前に初期化が必要。DXGI の生成順序としては、まず Factory を生成し、Factory から PC の Adapter (GPU) を取得。ここで取得した GPU を初期化して、表示用のフレームバッファ (SwapChain) の領域を確保。このフレームバッファに描画したものを Present することによって、SwapChain が画面表示の管理を行ってくれる。
Factory と Adapter の生成
Microsoft::WRL::ComPtr<IDXGIFactory6> dxgiFactory;
CreateDXGIFactory2(dxgiFactoryFlags, MY_IID_PPV_ARGS(&dxgiFactory));
IDXGIFactory を CreateDXGIFactory に渡すことで Factory が作られる。インターフェースや関数名に数字がついているが、これは過去に拡張された事を表している。現在のバージョン番号である。
Factory が作られれば Factory が持つ EnumAdapter などを呼び出して、IDXGIAdapter が取得できる。
取得した IDXGIAdapter を D3D12CreateDevice に渡せば、ここで初めて DirectX が初期化できる。ちなみに Adapter は無くても D3D12CreateDevice は初期化できるが、GPU の情報にアクセス出来るので作っておいた方が無難。
Swapchain
Factory から作られる、もう一つ重要なインターフェースが IDXGISwapChain である。これが無いと、レンダリング結果の表示が行えない。
ComPtr<IDXGISwapChain1> swapChain;
factory->CreateSwapChainForHwnd(
m_commandQueue.Get(), // Swap chain needs the queue so that it can force a flush on it.
Win32Application::GetHwnd(),
&swapChainDesc,
nullptr,
nullptr,
&swapChain
);
CreateSwapChain の引数には SwapChain Desc を渡す。この内容でフレームバッファが作られる。
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.BufferCount = FrameCount;
swapChainDesc.Width = m_width;
swapChainDesc.Height = m_height;
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
pChainDesc.SampleDesc.Count = 1;
CreateSwapChain を行ったときに VRAM 上にフレームバッファの領域が確保され、ここで確保したバッファを Render Target として使用することによってピクセルを更新する。
最後に SwapChain の Present メソッドを呼び出すことによって、バッファの内容がモニター等に出力される。
全画面モードや描画サイズの変更
ゲームの全画面表示や、描画解像度の変更などは DXGI を経由して行われる。例えば IDXGIFactory::MakeWindowAssociation を呼び出すことによって、Alt + Enter で全画面表示に移行できる。全画面遷移など、ウインドウが変更された場合 WndProc で WM_SIZE メッセージが来る。このメッセージによって、SwapChain->ResizeBuffers を呼び出せばフレームバッファのサイズ変更が行える。
フレームバッファが変わったら、他の RenderTarget の再生成も行う必要があるので、同じように WM_SIZE メッセージで処理することになる。
その他重要なインターフェース
IDXGIOutput
IDXGISwapChain から取得できる。GetDesc() を呼ぶ出すことによってディスプレイの表示解像度などの情報を取得することが出来る。
ComPtr<IDXGIOutput> pOutput;
ThrowIfFailed(pSwapChain->GetContainingOutput(&pOutput));
DXGI_OUTPUT_DESC Desc;
ThrowIfFailed(pOutput->GetDesc(&Desc));
2022年11月現在、IDXGIOutput6 が最新のインターフェースになっていて、GetDesc1() を呼び出すことで、ディスプレイのカラースペースや表示輝度などの情報をとることが出来る。
ComPtr<IDXGIOutput6> output6;
ThrowIfFailed(pOutput.As(&output6));
DXGI_OUTPUT_DESC1 desc1;
ThrowIfFailed(output6->GetDesc1(&desc1));
HDR に関してはこちらのドキュメントを参考。
過去の DirectX
昔の DirectX では、IDirect3DDevice が Present メソッドを持っていたみたい。DirectX9 までは DirectX API と低レベルの描画機能が一緒になっていたが、DirectX10 から DXGI として分離された。
Reference
https://learn.microsoft.com/ja-jp/windows/win32/direct3ddxgi/dx-graphics-dxgi
https://learn.microsoft.com/ja-jp/windows/uwp/gaming/moving-from-egl-to-dxgi