DirectX デバイス リソースの操作

Windows Microsoft Store DirectX ゲームにおける Microsoft DirectX グラフィックス インフラストラクチャ (DXGI) の役割について説明します。 DXGI は、低レベルのグラフィックスとグラフィックス アダプターのリソースを構成および管理するために使用される一連の API です。 それがなければ、ゲームのグラフィックスをウィンドウに描画する方法はありません。

このように DXGI を考えてみましょう。GPU に直接アクセスしてそのリソースを管理するには、アプリに記述する方法が必要です。 GPU に関して必要な最も重要な情報は、ピクセルを描画して、それらのピクセルを画面に送信できる場所です。 通常、これは "バック バッファー" と呼ばれます。これは GPU メモリ内の場所で、ピクセルを描画し、"反転" または "スワップ" して、更新信号で画面に送信します。 DXGI を使用すると、その場所とそのバッファーを使用する手段を取得できます (スワップ可能なバッファーのチェーンであるため、 スワップ チェーン と呼ばれ、複数のバッファリング戦略が可能になります)。

これを行うには、スワップ チェーンに書き込むためのアクセス権と、スワップ チェーンの現在のバック バッファーを表示するウィンドウへのハンドルが必要です。 また、2 つを接続して、要求時にオペレーティング システムがバック バッファーの内容でウィンドウを更新するようにする必要もあります。

画面に描画するための全体的なプロセスは次のとおりです。

  • アプリの 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 を使用する最初の手順は、 ID3D11Device と ID3D11DeviceContext のインスタンスとして表される Direct3D ハードウェア (GPU) のインターフェイスを取得することです。 前者は GPU リソースの仮想表現であり、後者はレンダリング パイプラインとプロセスのデバイスに依存しない抽象化です。 簡単に考える方法を次に示します。 ID3D11Device には、レンダリングが発生する前に、ピクセルの描画を開始するために必要な一連のリソースを取得して構成するために、頻繁に呼び出さないグラフィックス メソッドが含まれています。 一方、ID3D11DeviceContext には、バッファーやビューなどのリソースへの読み込み、出力マージャーとラスタライザーの状態の変更、シェーダーの管理、状態とシェーダーを介してこれらのリソースを渡した結果の描画など、すべてのフレームを呼び出すメソッドが含まれています。

このプロセスには、機能レベルの設定という 1 つの非常に重要な部分があります。 機能レベルは、アプリがサポートするハードウェアの最小レベルを 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_9_1,
    D3D_FEATURE_LEVEL_9_2,
    D3D_FEATURE_LEVEL_9_3,
    D3D_FEATURE_LEVEL_10_0,
    D3D_FEATURE_LEVEL_10_1,
    D3D_FEATURE_LEVEL_11_0,
    D3D_FEATURE_LEVEL_11_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);

スワップ チェーンを作成する

Ok: 描画するウィンドウがあり、データを送信して GPU にコマンドを渡すインターフェイスがあります。 それでは、それらをまとめる方法を見てみましょう。

まず、スワップ チェーンのプロパティに使用する値を DXGI に指示します。 これを行うには、 DXGI_SWAP_CHAIN_DESC 構造体を使用します。 デスクトップ アプリでは、次の 6 つのフィールドが特に重要です。

  • ウィンドウ: スワップ チェーンが全画面表示であるか、ウィンドウにクリップされているかを示します。 前に作成したウィンドウにスワップ チェーンを配置するには、これを TRUE に設定します。
  • BufferUsage: これをDXGI_USAGE_RENDER_TARGET_OUTPUTに設定します。 これは、スワップ チェーンが描画サーフェスになり、Direct3D レンダー ターゲットとして使用することを示します。
  • SwapEffect: これをDXGI_SWAP_EFFECT_FLIP_SEQUENTIALに設定します。
  • 形式: DXGI_FORMAT_B8G8R8A8_UNORM形式では、32 ビットの色を指定します。3 つの RGB カラー チャネルごとに 8 ビット、アルファ チャネルに 8 ビットを指定します。
  • BufferCount: 引き裂かないように、従来のダブルバッファー動作の場合は、これを 2 に設定します。 グラフィックス コンテンツが 1 つのフレームをレンダリングするために複数のモニター更新サイクルを要する場合は、バッファー数を 3 に設定します (たとえば、しきい値が 16 ミリ秒を超える場合は 60 Hz)。
  • SampleDesc: このフィールドはマルチサンプリングを制御します。 フリップ モデル スワップ チェーンの場合は 、Count を 1 に、 Quality を 0 に設定します。 (フリップ モデル スワップ チェーンでマルチサンプリングを使用するには、別のマルチサンプリング レンダー ターゲットに描画し、そのターゲットをスワップ チェーンに解決してから、そのターゲットを表示する直前に解決します。コード例は、Windows Microsoft Store アプリのマルチサンプリングで提供されています。

スワップ チェーンの構成を指定した後、スワップ チェーンを作成するには、Direct3D デバイス (およびデバイス コンテキスト) を作成したのと同じ DXGI ファクトリを使用する必要があります。

**短い形式: **

前に作成した ID3D11Device 参照を取得します。 それを IDXGIDevice3 にアップキャストし (まだ実行していない場合)、 IDXGIDevice::GetAdapter を呼び出して DXGI アダプターを取得します。 次のコード サンプルに示すように、 IDXGIFactory2::GetParent (IDXGIFactory2IDXGIObject から継承) を呼び出して、そのアダプターの親ファクトリを取得します。これで、 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
    );

そして、それはあなたがウィンドウにピクセルを描画するために何もないから行く方法です! 最初に、ピクセルの描画を開始するために必要なコア リソースを DXGI を通じて DirectX がどのように管理されるかを理解することをお勧めします。

次に、グラフィックス パイプラインの構造を見ていきます。 DirectX アプリ テンプレートのレンダリング パイプラインを理解するを参照してください。

次へ

シェーダーとシェーダー リソースを操作する