設定 DirectX 資源並顯示影像
在本文中,我們將說明如何建立 Direct3D 裝置、交換鏈結和轉譯目標檢視,以及如何將轉譯的影像呈現至顯示器。
目標: 在 C++ 通用 Windows 平台 (UWP) 應用程式中設定 DirectX 資源,以及顯示純色。
必要條件
我們假設您已熟悉 C++ 語言。 此外,您對於圖形程式設計概念也需要有基本的認識。
完成時間: 20 分鐘。
指示
1. 使用 ComPtr 宣告 Direct3D 介面變數
我們會使用 Windows 執行階段 C++ 範本庫 (WRL) 的 ComPtr 智慧型指標範本宣告 Direct3D 介面變數,因此即使有例外狀況也能安全地管理這些變數的存留期。 接著,我們可使用這些變數存取 ComPtr 類別 及其成員。 例如:
ComPtr<ID3D11RenderTargetView> m_renderTargetView;
m_d3dDeviceContext->OMSetRenderTargets(
1,
m_renderTargetView.GetAddressOf(),
nullptr // Use no depth stencil.
);
如果您使用 ComPtr 宣告 ID3D11RenderTargetView,則可使用 ComPtr 的 GetAddressOf 方法取得 ID3D11RenderTargetView (**ID3D11RenderTargetView) 指標的位址,以傳遞至 ID3D11DeviceContext::OMSetRenderTargets。 OMSetRenderTargets 會將轉譯目標繫結至輸出合併階段,以將轉譯目標指定為輸出目標。
範例應用程式啟動後會初始化並載入,然後準備好執行。
2. 建立 Direct3D 裝置
如要使用 Direct3D API 轉譯場景,我們必須先建立代表顯示卡的 Direct3D 裝置。 若要建立 Direct3D 裝置,我們要呼叫 D3D11CreateDevice 函式。 我們指定 D3D_FEATURE_LEVEL 值陣列中的層級 9.1 到 11.1。 Direct3D 會依序逐步處理陣列,並傳回支援的最高功能層級。 因此,為取得可用的最高功能層級,我們會從最高到最低列出 D3D_FEATURE_LEVEL 陣列項目。 我們會將 D3D11_CREATE_DEVICE_BGRA_SUPPORT 旗標傳遞至 Flags 參數,讓 Direct3D 資源與 Direct2D 互通。 如果我們使用偵錯組建,則也會傳遞 D3D11_CREATE_DEVICE_DEBUG 旗標。 如需有關偵錯應用程式的詳細資訊,請參閱<使用偵錯層偵錯應用程式>。
我們會透過查詢 D3D11CreateDevice 傳回的 Direct3D 11 裝置和裝置內容,取得 Direct3D 11.1 裝置 (ID3D11Device1) 和裝置內容 (ID3D11DeviceContext1)。
// First, create the Direct3D device.
// This flag is required in order to enable compatibility with Direct2D.
UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
#if defined(_DEBUG)
// If the project is in a debug build, enable debugging via SDK Layers with this flag.
creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
// This array defines the ordering of feature levels that D3D should attempt to create.
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_1
};
ComPtr<ID3D11Device> d3dDevice;
ComPtr<ID3D11DeviceContext> d3dDeviceContext;
DX::ThrowIfFailed(
D3D11CreateDevice(
nullptr, // Specify nullptr to use the default adapter.
D3D_DRIVER_TYPE_HARDWARE,
nullptr, // leave as nullptr if hardware is used
creationFlags, // optionally set debug and Direct2D compatibility flags
featureLevels,
ARRAYSIZE(featureLevels),
D3D11_SDK_VERSION, // always set this to D3D11_SDK_VERSION
&d3dDevice,
nullptr,
&d3dDeviceContext
)
);
// Retrieve the Direct3D 11.1 interfaces.
DX::ThrowIfFailed(
d3dDevice.As(&m_d3dDevice)
);
DX::ThrowIfFailed(
d3dDeviceContext.As(&m_d3dDeviceContext)
);
3. 建立交換鏈結
接下來,我們會建立可讓裝置用於轉譯和顯示的交換鏈結。 我們會宣告並初始化 DXGI_SWAP_CHAIN_DESC1 結構,以描述交換鏈結。 接著,我們將交換鏈結設為翻轉模型 (也就是在 SwapEffect 成員中已設定 DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL 值的交換鏈結),然後將 Format 成員設為 DXGI_FORMAT_B8G8R8A8_UNORM。 我們將 DXGI_SAMPLE_DESC 結構 (由 SampleDesc 成員指定) 的 Count 成員設為 1,並將 DXGI_SAMPLE_DESC 的 Quality 成員設為 0,因為翻轉模型不支援多重取樣消除鋸齒 (MSAA) 功能。 我們將 BufferCount 成員設為 2,這樣交換鏈結即可使用前端緩衝區呈現給顯示裝置,並使用作為轉譯目標的後端緩衝區。
我們會透過查詢 Direct3D 11.1 裝置取得基礎 DXGI 裝置。 降低耗電對電池供電裝置 (例如:膝上型電腦和平板電腦) 而言很重要,為將耗電量降到最低,我們會呼叫 IDXGIDevice1::SetMaximumFrameLatency 方法,其中,DXGI 可排入佇列的後端緩衝區畫面格數上限為 1。 這可確保應用程式只在垂直空白 (vertical blank) 後才轉譯。
最後,若要建立交換鏈結,我們需要從 DXGI 裝置取得上層處理站。 我們呼叫 IDXGIDevice::GetAdapter 以取得裝置介面卡,然後呼叫介面卡上的 IDXGIObject::GetParent 以取得上層處理站 (IDXGIFactory2)。 若要建立交換鏈結,我們會使用交換鏈結描述項和應用程式的核心視窗呼叫 IDXGIFactory2::CreateSwapChainForCoreWindow。
// If the swap chain does not exist, create it.
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {0};
swapChainDesc.Stereo = false;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.Scaling = DXGI_SCALING_NONE;
swapChainDesc.Flags = 0;
// Use automatic sizing.
swapChainDesc.Width = 0;
swapChainDesc.Height = 0;
// This is the most common swap chain format.
swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
// Don't use multi-sampling.
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
// Use two buffers to enable the flip effect.
swapChainDesc.BufferCount = 2;
// We recommend using this swap effect for all applications.
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
// Once the swap chain description is configured, it must be
// created on the same adapter as the existing D3D Device.
// First, retrieve the underlying DXGI Device from the D3D Device.
ComPtr<IDXGIDevice2> dxgiDevice;
DX::ThrowIfFailed(
m_d3dDevice.As(&dxgiDevice)
);
// Ensure that DXGI does not queue more than one frame at a time. This both reduces
// latency and ensures that the application will only render after each VSync, minimizing
// power consumption.
DX::ThrowIfFailed(
dxgiDevice->SetMaximumFrameLatency(1)
);
// Next, get the parent factory from the DXGI Device.
ComPtr<IDXGIAdapter> dxgiAdapter;
DX::ThrowIfFailed(
dxgiDevice->GetAdapter(&dxgiAdapter)
);
ComPtr<IDXGIFactory2> dxgiFactory;
DX::ThrowIfFailed(
dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory))
);
// Finally, create the swap chain.
CoreWindow^ window = m_window.Get();
DX::ThrowIfFailed(
dxgiFactory->CreateSwapChainForCoreWindow(
m_d3dDevice.Get(),
reinterpret_cast<IUnknown*>(window),
&swapChainDesc,
nullptr, // Allow on all displays.
&m_swapChain
)
);
4. 建立轉譯目標檢視
若要將圖形轉譯至視窗,我們需要建立轉譯目標檢視。 我們呼叫 IDXGISwapChain::GetBuffer,取得交換鏈結的後端緩衝區,以在建立轉譯目標檢視時使用。 我們將後端緩衝區指定為 2D 紋理 (ID3D11Texture2D)。 若要建立轉譯目標檢視,我們會使用交換鏈結的後端緩衝區呼叫 ID3D11Device::CreateRenderTargetView。 我們必須將檢視區 (D3D11_VIEWPORT) 指定為完整大小的交換鏈結後端緩衝區,以繪製至整個核心視窗。 我們會在對 ID3D11DeviceContext::RSSetViewports 的呼叫中使用檢視區,以將檢視區繫結至管線的轉譯器階段。 轉譯器階段會將向量資訊轉換成點陣影像。 在此案例中,我們只是要顯示純色,因此不需要轉換。
// Once the swap chain is created, create a render target view. This will
// allow Direct3D to render graphics to the window.
ComPtr<ID3D11Texture2D> backBuffer;
DX::ThrowIfFailed(
m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer))
);
DX::ThrowIfFailed(
m_d3dDevice->CreateRenderTargetView(
backBuffer.Get(),
nullptr,
&m_renderTargetView
)
);
// After the render target view is created, specify that the viewport,
// which describes what portion of the window to draw to, should cover
// the entire window.
D3D11_TEXTURE2D_DESC backBufferDesc = {0};
backBuffer->GetDesc(&backBufferDesc);
D3D11_VIEWPORT viewport;
viewport.TopLeftX = 0.0f;
viewport.TopLeftY = 0.0f;
viewport.Width = static_cast<float>(backBufferDesc.Width);
viewport.Height = static_cast<float>(backBufferDesc.Height);
viewport.MinDepth = D3D11_MIN_DEPTH;
viewport.MaxDepth = D3D11_MAX_DEPTH;
m_d3dDeviceContext->RSSetViewports(1, &viewport);
5. 呈現轉譯的影像
我們進入無限迴圈,以持續轉譯和顯示場景。
在此迴圈中,我們呼叫:
- ID3D11DeviceContext::OMSetRenderTargets:將轉譯目標指定為輸出目標。
- ID3D11DeviceContext::ClearRenderTargetView:將轉譯目標清除為純色。
- IDXGISwapChain::Present:將轉譯的影像呈現至視窗。
由於我們先前將畫面格延遲上限設為 1,Windows 一般會將轉譯迴圈減緩至螢幕重新整理率,通常是大約 60 Hz。 應用程式呼叫 Present 時,Windows 會讓應用程式進入睡眠狀態,以減緩轉譯迴圈速度。 Windows 會讓應用程式進入睡眠狀態,直到螢幕重新整理。
// Enter the render loop. Note that UWP apps should never exit.
while (true)
{
// Process events incoming to the window.
m_window->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
// Specify the render target we created as the output target.
m_d3dDeviceContext->OMSetRenderTargets(
1,
m_renderTargetView.GetAddressOf(),
nullptr // Use no depth stencil.
);
// Clear the render target to a solid color.
const float clearColor[4] = { 0.071f, 0.04f, 0.561f, 1.0f };
m_d3dDeviceContext->ClearRenderTargetView(
m_renderTargetView.Get(),
clearColor
);
// Present the rendered image to the window. Because the maximum frame latency is set to 1,
// the render loop will generally be throttled to the screen refresh rate, typically around
// 60 Hz, by sleeping the application on Present until the screen is refreshed.
DX::ThrowIfFailed(
m_swapChain->Present(1, 0)
);
}
6. 調整應用程式視窗和交換鏈結緩衝區的大小
如果應用程式視窗大小已變更,則應用程式必須調整交換鏈結緩衝區的大小,並重新建立轉譯目標檢視,然後呈現已調整大小的轉譯影像。 若要調整交換鏈結緩衝區的大小,我們會呼叫 IDXGISwapChain::ResizeBuffers。 在此呼叫中,緩衝區數目和緩衝區格式保持不變 (BufferCount 參數為 2;NewFormat 參數為 DXGI_FORMAT_B8G8R8A8_UNORM)。 我們要讓交換鏈結後端緩衝區的大小與重新調整的視窗大小相同。 調整交換鏈結的緩衝區大小之後,我們會建立新的轉譯目標,並在初始化應用程式時,以類似的方法呈現新的轉譯影像。
// If the swap chain already exists, resize it.
DX::ThrowIfFailed(
m_swapChain->ResizeBuffers(
2,
0,
0,
DXGI_FORMAT_B8G8R8A8_UNORM,
0
)
);
摘要和後續步驟
我們已建立 Direct3D 裝置、交換鏈結和轉譯目標檢視,並將轉譯的影像呈現至顯示器。
接下來,我們也會在顯示器上繪製三角形。