使用 DirectX 裝置資源
瞭解 Windows 市集 DirectX 遊戲中 Microsoft DirectX 圖形基礎結構 (DXGI) 的角色。 DXGI 是一組 API,可用來設定和管理低階圖形和圖形介面卡資源。 如果沒有它,你就無法將遊戲的圖形繪製到視窗。
以這種方式思考 DXGI:若要直接存取 GPU 並管理其資源,您必須有方法向您的應用程式描述它。 GPU 所需的最重要的資訊是繪製圖元的位置,以便將這些圖元傳送到螢幕。 這通常稱為「背景緩衝區」,這是 GPU 記憶體中您可以繪製圖元的位置,然後讓其「翻轉」或「交換」,並在重新整理訊號上傳送至畫面。 DXGI 可讓您取得該位置與使用該緩衝區的方法(稱為 交換鏈 結,因為它是可交換緩衝區的鏈結,允許多個緩衝策略)。
若要這樣做,您需要存取權來寫入交換鏈結,以及將顯示交換鏈結目前背景緩衝區之視窗的控制碼。 您也需要連線兩者,以確保作業系統會在您要求其執行此動作時,使用背景緩衝區的內容重新整理視窗。
繪製到畫面的整體程式如下所示:
- 取得應用程式的 CoreWindow 。
- 取得 Direct3D 裝置和內容的介面。
- 建立交換鏈結,以在 CoreWindow 中顯示轉譯的 影像。
- 建立繪製的轉譯目標,並以圖元填入它。
- 呈現交換鏈結!
為您的應用程式建立視窗
我們需要做的第一件事是建立視窗。 首先,藉由填入 WNDCLASS 的 實例來建立視窗類別,然後使用 RegisterClass 註冊它。 視窗類別包含視窗的基本屬性,包括所使用的圖示、靜態訊息處理函式(稍後會更多內容),以及視窗類別的唯一名稱。
if(m_hInstance == NULL)
m_hInstance = (HINSTANCE)GetModuleHandle(NULL);
HICON hIcon = NULL;
WCHAR szExePath[MAX_PATH];
GetModuleFileName(NULL, szExePath, MAX_PATH);
// If the icon is NULL, then use the first one found in the exe
if(hIcon == NULL)
hIcon = ExtractIcon(m_hInstance, szExePath, 0);
// Register the windows class
WNDCLASS wndClass;
wndClass.style = CS_DBLCLKS;
wndClass.lpfnWndProc = MainClass::StaticWindowProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = m_hInstance;
wndClass.hIcon = hIcon;
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = m_windowClassName.c_str();
if(!RegisterClass(&wndClass))
{
DWORD dwError = GetLastError();
if(dwError != ERROR_CLASS_ALREADY_EXISTS)
return HRESULT_FROM_WIN32(dwError);
}
接下來,您會建立視窗。 我們也需要提供視窗的大小資訊,以及我們剛才建立的視窗類別名稱。 當您呼叫 CreateWindow 時,您會回到稱為 HWND 之視窗的不透明指標;您必須保留 HWND 指標,並在任何需要參考視窗時使用它,包括終結或重新建立視窗,以及在建立用來在視窗中繪製的 DXGI 交換鏈結時(特別重要)。
m_rc;
int x = CW_USEDEFAULT;
int y = CW_USEDEFAULT;
// No menu in this example.
m_hMenu = NULL;
// This example uses a non-resizable 640 by 480 viewport for simplicity.
int nDefaultWidth = 640;
int nDefaultHeight = 480;
SetRect(&m_rc, 0, 0, nDefaultWidth, nDefaultHeight);
AdjustWindowRect(
&m_rc,
WS_OVERLAPPEDWINDOW,
(m_hMenu != NULL) ? true : false
);
// Create the window for our viewport.
m_hWnd = CreateWindow(
m_windowClassName.c_str(),
L"Cube11",
WS_OVERLAPPEDWINDOW,
x, y,
(m_rc.right-m_rc.left), (m_rc.bottom-m_rc.top),
0,
m_hMenu,
m_hInstance,
0
);
if(m_hWnd == NULL)
{
DWORD dwError = GetLastError();
return HRESULT_FROM_WIN32(dwError);
}
Windows 傳統型應用程式模型包含連結至 Windows 訊息迴圈。 您必須撰寫 「StaticWindowProc」 函式來處理視窗化事件,以將主要程式迴圈從這個攔截基礎。 這必須是靜態函式,因為 Windows 會在任何類別實例的內容之外呼叫它。 以下是靜態訊息處理函式的簡單範例。
LRESULT CALLBACK MainClass::StaticWindowProc(
HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
switch(uMsg)
{
case WM_CLOSE:
{
HMENU hMenu;
hMenu = GetMenu(hWnd);
if (hMenu != NULL)
{
DestroyMenu(hMenu);
}
DestroyWindow(hWnd);
UnregisterClass(
m_windowClassName.c_str(),
m_hInstance
);
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
這個簡單的範例只會檢查程式結束條件: WM_CLOSE 、要求關閉視窗時傳送,以及 WM_DESTROY ,這會在視窗實際從畫面中移除之後傳送。 完整、生產應用程式也需要處理其他視窗化事件—如需視窗化事件的完整清單,請參閱 視窗通知 。
主要程式迴圈本身必須藉由讓 Windows 有機會執行靜態訊息程式來認可 Windows 訊息。 藉由分叉行為協助程式有效率地執行:如果每個反復專案都可以使用,則應該選擇處理新的 Windows 訊息,如果佇列中沒有訊息,則應該轉譯新的框架。 以下是一個非常簡單的範例:
bool bGotMsg;
MSG msg;
msg.message = WM_NULL;
PeekMessage(&msg, NULL, 0U, 0U, PM_NOREMOVE);
while (WM_QUIT != msg.message)
{
// Process window events.
// Use PeekMessage() so we can use idle time to render the scene.
bGotMsg = (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE) != 0);
if (bGotMsg)
{
// Translate and dispatch the message
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
// Update the scene.
renderer->Update();
// Render frames during idle time (when no messages are waiting).
renderer->Render();
// Present the frame to the screen.
deviceResources->Present();
}
}
取得 Direct3D 裝置和內容的介面
使用 Direct3D 的第一個步驟是取得 Direct3D 硬體 (GPU) 的介面,以 ID3D11Device 和 ID3D11DeviceCoNtext 實例 表示。 前者是 GPU 資源的虛擬標記法,後者是轉譯管線和進程的裝置無關的抽象概念。 以下是一個簡單的方法 :ID3D11Device 包含您經常呼叫的圖形方法,通常在任何轉譯發生之前,取得並設定開始繪製圖元所需的資源集。 另一方面,ID3D11DeviceCoNtext 包含您呼叫每個畫面的方法:載入緩衝區和檢視和其他資源、變更輸出合併和轉譯器狀態、管理著色器,以及繪製透過狀態和著色器傳遞這些資源的結果。
此程式有一個非常重要的部分:設定功能層級。 功能層級會告知 DirectX 應用程式支援的最低硬體層級,D3D_FEATURE_LEVEL_9_1為最低功能集,D3D_FEATURE_LEVEL_11_1為目前最高。 如果您想要達到最廣泛的物件,您應該至少支援 9_1。 花一些時間閱讀 Direct3D 功能層級,並自行評估您希望遊戲支援的最低和最大功能層級 ,並瞭解您選擇的含意。
取得 Direct3D 裝置和裝置內容的參考(指標),並將其儲存為 DeviceResources 實例上的 類別層級變數(作為 ComPtr 智慧型指標)。 每當您需要存取 Direct3D 裝置或裝置內容時,請使用這些參考。
D3D_FEATURE_LEVEL levels[] = {
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_2,
D3D_FEATURE_LEVEL_9_1,
};
// This flag adds support for surfaces with a color-channel ordering different
// from the API default. It is required for compatibility with Direct2D.
UINT deviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
#if defined(DEBUG) || defined(_DEBUG)
deviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
// Create the Direct3D 11 API device object and a corresponding context.
Microsoft::WRL::ComPtr<ID3D11Device> device;
Microsoft::WRL::ComPtr<ID3D11DeviceContext> context;
hr = D3D11CreateDevice(
nullptr, // Specify nullptr to use the default adapter.
D3D_DRIVER_TYPE_HARDWARE, // Create a device using the hardware graphics driver.
0, // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE.
deviceFlags, // Set debug and Direct2D compatibility flags.
levels, // List of feature levels this app can support.
ARRAYSIZE(levels), // Size of the list above.
D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Windows Store apps.
&device, // Returns the Direct3D device created.
&m_featureLevel, // Returns feature level of device created.
&context // Returns the device immediate context.
);
if (FAILED(hr))
{
// Handle device interface creation failure if it occurs.
// For example, reduce the feature level requirement, or fail over
// to WARP rendering.
}
// Store pointers to the Direct3D 11.1 API device and immediate context.
device.As(&m_pd3dDevice);
context.As(&m_pd3dDeviceContext);
建立交換鏈結
沒問題:您有一個要繪製的視窗,而且有介面可傳送資料,並將命令提供給 GPU。 現在讓我們看看如何將它們結合在一起。
首先,您會告訴 DXGI 要用於交換鏈結屬性的值。 使用 DXGI_SWAP_CHAIN_DESC 結構來執行此動作。 六個欄位對於傳統型應用程式特別重要:
- 視窗: 指出交換鏈結是全螢幕還是裁剪到視窗。 將此設定為 TRUE,以將交換鏈結放在您稍早建立的視窗中。
- BufferUsage :將此設定為DXGI_USAGE_RENDER_TARGET_OUTPUT。 這表示交換鏈結會是繪圖介面,可讓您將它當做 Direct3D 轉譯目標使用。
- SwapEffect :將此設定為DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL。
- 格式 :DXGI_FORMAT_B8G8R8A8_UNORM格式會指定 32 位色彩:三個 RGB 色板的 8 位,Alpha 色板各指定 8 位。
- BufferCount :針對傳統的雙緩衝行為將此設定為 2,以避免撕裂。 如果您的圖形內容需要一個以上的監視器重新整理週期來轉譯單一畫面,請將緩衝區計數設定為 3(例如,閾值超過 16 毫秒)。
- SampleDesc :此欄位會控制多重取樣。 將 [計數 ] 設定為 1,並將 [品質 ] 設定 為 0 以用於翻轉模型交換鏈結。 (若要搭配翻轉模型交換鏈結使用多重取樣,請在不同的多重取樣轉譯目標上繪製,然後在呈現之前,將該目標解析為交換鏈結。範例程式碼是在 Windows 市集應用程式中 的 Multisampling 中提供 。
指定交換鏈結的組態之後,您必須使用相同的 DXGI 處理站來建立 Direct3D 裝置(和裝置內容),才能建立交換鏈結。
簡短格式:
取得您先前建立的 ID3D11Device 參考。 將它向上轉型為 IDXGIDevice3 (如果您尚未這麼做),然後呼叫 IDXGIDevice::GetAdapter 以取得 DXGI 配接器 。 藉由呼叫 IDXGIAdapter::GetParent 取得該配接器的父處理站( IDXGIAdapter 繼承自 IDXGIObject ),現在您可以使用該處理站呼叫 CreateSwapChainForHwnd 來建立交換鏈結 ,如下列程式碼範例所示。
DXGI_SWAP_CHAIN_DESC desc;
ZeroMemory(&desc, sizeof(DXGI_SWAP_CHAIN_DESC));
desc.Windowed = TRUE; // Sets the initial state of full-screen mode.
desc.BufferCount = 2;
desc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.SampleDesc.Count = 1; //multisampling setting
desc.SampleDesc.Quality = 0; //vendor-specific flag
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
desc.OutputWindow = hWnd;
// Create the DXGI device object to use in other factories, such as Direct2D.
Microsoft::WRL::ComPtr<IDXGIDevice3> dxgiDevice;
m_pd3dDevice.As(&dxgiDevice);
// Create swap chain.
Microsoft::WRL::ComPtr<IDXGIAdapter> adapter;
Microsoft::WRL::ComPtr<IDXGIFactory> factory;
hr = dxgiDevice->GetAdapter(&adapter);
if (SUCCEEDED(hr))
{
adapter->GetParent(IID_PPV_ARGS(&factory));
hr = factory->CreateSwapChain(
m_pd3dDevice.Get(),
&desc,
&m_pDXGISwapChain
);
}
如果您剛開始,最好使用此處顯示的組態。 現在,如果您已經熟悉舊版 DirectX,您可能會問:「為什麼我們不同時建立裝置和交換鏈結,而不是走回所有這些類別?答案是效率:交換鏈結是 Direct3D 裝置資源,而裝置資源會系結至建立它們的特定 Direct3D 裝置。 如果您使用新的交換鏈結建立新的裝置,則必須使用新的 Direct3D 裝置重新建立所有裝置資源。 因此,您可以使用相同的處理站建立交換鏈結(如上所示),您可以重新建立交換鏈結,然後使用您已載入的 Direct3D 裝置資源繼續!
現在您已從作業系統取得視窗、存取 GPU 及其資源的方法,以及用來顯示轉譯結果的交換鏈結。 剩下的就是把整件事連接在一起!
建立繪圖的轉譯目標
著色器管線需要資源來繪製圖元。 建立此資源最簡單的方式是將 ID3D11Texture2D 資源定義為 要繪製的圖元著色器背景緩衝區,然後將該紋理讀入交換鏈結。
若要這樣做,您可以建立轉譯目標 檢視 。 在 Direct3D 中,檢視是存取特定資源的方法。 在此情況下,檢視可讓圖元著色器在完成每個圖元作業時寫入紋理。
讓我們看看此程式碼。 當您在交換鏈結上設定DXGI_USAGE_RENDER_TARGET_OUTPUT時,該連結可讓基礎 Direct3D 資源當做繪圖介面使用。 因此,若要取得轉譯目標檢視,我們只需要從交換鏈結取得背景緩衝區,並建立系結至後端緩衝區資源的轉譯目標檢視。
hr = m_pDXGISwapChain->GetBuffer(
0,
__uuidof(ID3D11Texture2D),
(void**) &m_pBackBuffer);
hr = m_pd3dDevice->CreateRenderTargetView(
m_pBackBuffer.Get(),
nullptr,
m_pRenderTarget.GetAddressOf()
);
m_pBackBuffer->GetDesc(&m_bbDesc);
同時建立 深度樣板緩衝區 。 深度樣板緩衝區只是 ID3D11Texture2D 資源的特定形式 ,通常用來根據相機中物件距離,判中斷點陣化期間哪些圖元具有繪製優先順序。 深度樣板緩衝區也可用於樣板效果,其中特定圖元會在點陣化期間捨棄或忽略。 這個緩衝區的大小必須與轉譯目標相同。 請注意,您無法讀取或轉譯到畫面緩衝區深度樣板紋理,因為著色器管線在最終點陣化之前和期間會獨佔使用。
同時建立深度樣板緩衝區的檢視做為 ID3D11DepthStencilView 。 此檢視會告知著色器管線如何解譯基礎 ID3D11Texture2D 資源 -- 因此,如果您未提供此檢視,則不會執行每個圖元深度測試,而且場景中的物件至少看起來有點內在!
CD3D11_TEXTURE2D_DESC depthStencilDesc(
DXGI_FORMAT_D24_UNORM_S8_UINT,
static_cast<UINT> (m_bbDesc.Width),
static_cast<UINT> (m_bbDesc.Height),
1, // This depth stencil view has only one texture.
1, // Use a single mipmap level.
D3D11_BIND_DEPTH_STENCIL
);
m_pd3dDevice->CreateTexture2D(
&depthStencilDesc,
nullptr,
&m_pDepthStencil
);
CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D);
m_pd3dDevice->CreateDepthStencilView(
m_pDepthStencil.Get(),
&depthStencilViewDesc,
&m_pDepthStencilView
);
最後一個步驟是建立檢視區。 這會定義畫面上顯示背景緩衝區的可見矩形;您可以變更檢視區的參數,來變更畫面上顯示的緩衝區部分。 在全螢幕交換鏈結的情況下,此程式碼會以整個視窗大小或螢幕解析度為目標。 為了有趣,請變更提供的座標值,並觀察結果。
ZeroMemory(&m_viewport, sizeof(D3D11_VIEWPORT));
m_viewport.Height = (float) m_bbDesc.Height;
m_viewport.Width = (float) m_bbDesc.Width;
m_viewport.MinDepth = 0;
m_viewport.MaxDepth = 1;
m_pd3dDeviceContext->RSSetViewports(
1,
&m_viewport
);
這就是您從無到有到在視窗中繪製圖元的方式! 一開始,最好熟悉 DirectX 如何透過 DXGI 管理開始繪製圖元所需的核心資源。
接下來,您將查看圖形管線的結構;請參閱 瞭解 DirectX 應用程式範本的轉譯管線 。
相關主題
-
下一步