1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

共有テクスチャ取得用として適切なDirectXデバイスを検索する

Posted at

以前書いた記事:DirectXでテクスチャを別プロセスに共有するの補足記事です。

N.Mです。以前の記事でDirectXのテクスチャを共有した際、注意点として以下のことを挙げました。

それはそうと思うかもしれませんが、送信元プロセスと送信先プロセスで同じGPUを使用している必要があります。違うGPUを使用している場合は、送信先のプロセスでOpenSharedResourceByNameを呼び出したところで、「引数が不正である」(E_INVALIDARG)といったエラーが返ってきます。

複数のGPUを積んでいるPCで起こりうる問題ですが、実際のところWindows側で優先するGPUを自動選択する設定もできるので、この部分を対策しないと一部の環境に対応できないことがわかりました。1

ウィンドウをキャプチャし、仮想カメラに送る自作ツール: NM_WindowCaptureVirtualCamera2でも修正対応を行ったので、その対応を紹介したいと思います。

対応

共有テクスチャを受け取る送信先のプロセスでDirectXのデバイスを作成する際に、以下を行いました。

  1. 一度使用可能なすべてのGPUに対してデバイスを走査する
  2. デバイスを一つずつ見ていき、共有テクスチャの取得に成功するか確認する。取得に成功したデバイスを以降の処理で使用するようにする

共有テクスチャを作る送信元のプロセスは、D3D11CreateDeviceでデフォルトのデバイスを作成すれば大丈夫です。

1のすべてのGPUを走査する方法ですが、dxgiの機能を利用すれば実現できました。その際に、以下の記事を参考にしています。

サンプルコード

dxgiの機能を使用するため、以下の対応が必要です

  • dxgi.hのインクルード
  • dxgi.libのリンク(#pragma comment(lib, "dxgi.lib")
// winrt::com_ptrでポインタを管理することを想定しています。
// 引数についてはcom_ptrのputメソッドで取得できるダブルポインタを渡すことを想定しています。

void findDeviceAndGetSharedTexture(ID3D11Device1** i_dxDevice,
    ID3D11DeviceContext** i_dxDeviceContext
    ID3D11Texture2D** i_sharedTexture)
{
    // 共有テクスチャを取得できるデバイスを探し、見つかればテクスチャやデバイスコンテキストを作成
    UINT createDeviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
#ifdef _DEBUG
    createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

    com_ptr<IDXGIFactory1> factory(nullptr);
    com_ptr<IDXGIAdapter1> adapter(nullptr);
    com_ptr<ID3D11Device> device(nullptr);
    D3D_FEATURE_LEVEL d3dFeatures[7] = {
        D3D_FEATURE_LEVEL_11_1
    };

    UINT adapterIdx = 0;
    HRESULT hr = S_OK;
    if (CreateDXGIFactory1(IID_PPV_ARGS(factory.put())) != S_OK)
    {
        return;
    }
    while (factory->EnumAdapters1(adapterIdx, adapter.put()) != DXGI_ERROR_NOT_FOUND)
    {
        // adapterIdxのインデックスに割り当てられているアダプタに対して、デバイスを作成する
        hr = D3D11CreateDevice(adapter.get(), D3D_DRIVER_TYPE_UNKNOWN,
            nullptr, createDeviceFlags, d3dFeatures, 1, D3D11_SDK_VERSION,
            device.put(), nullptr, i_dxDeviceContext);
        if (hr != S_OK)
        {
            adapterIdx++;
            continue;
        }

        hr = device->QueryInterface(IID_PPV_ARGS(i_dxDevice));
        if (hr != S_OK)
        {
            adapterIdx++;
            continue;
        }

        // 作成されたデバイスで共有テクスチャの取得を試み、成功した場合はそのDirectXのデバイスを以降使用する
        // SHARED_CAPTURE_WINDOW_TEXTURE_PATHには共有テクスチャのハンドル名("Global\\****")を入れています。
        hr = _dxDevice->OpenSharedResourceByName(SHARED_CAPTURE_WINDOW_TEXTURE_PATH,
            DXGI_SHARED_RESOURCE_READ, IID_PPV_ARGS(i_sharedTexture));
        if (hr != S_OK)
        {
            adapterIdx++;
            continue;
        }

        break;
    }
}

解説

使用可能なDirectXのデバイスはIDXGIFactory1EnumAdapters1メソッドで走査できます。IDXGIFactory1CreateDXGIFactory1でそのインスタンスを作成できます。

EnumAdapters1は第1引数で渡すインデックスに対応したアダプタIDXGIAdapter1を取得できます。インデックスを0から順に1ずつ増やすことで、使用可能なGPUに紐づいたアダプタすべてを走査できます。走査が完了するとEnumAdapters1DXGI_ERROR_NOT_FOUNDを返します。

ここで取得されたIDXGIAdapter1D3D11CreateDeviceの第1引数に渡すことができ、渡すことでアダプタに紐づいたGPUを使用するDirectXのデバイス(ID3D11Device)を作成できます。このように作成されたDirectXのデバイスは、一度QueryInterfaceID3D11Device1に変換し、OpenSharedResourceByNameメソッドで共有テクスチャの取得を試みます。

エラーが発生せずに取得できればwhile文を抜け、その時点で作成、取得したデバイスやデバイスコンテキスト、共有テクスチャを以降の処理で使用するようにします。エラーが発生した場合はインデックスを更新し、continueで次のアダプタに対して同様の確認を行います。

注意点

最後に1のつくIDXGIFactory1EnumAdapters1などを使用する

似たものとしてIDXGIFactoryEnumAdaptersといったものがあります。これらはDXGI 1.0の機能を使用したものです。共有テクスチャはDXGI 1.1から使用できるようになった機能なので、IDXGIFactoryEnumAdaptersを使用すると、共有テクスチャ使用時に必ずエラーが発生します。

そのため、DXGI 1.1に対応したIDXGIFactory1EnumAdapters1などを使用する必要があります。

D3D11CreateDeviceの引数にD3D_DRIVER_TYPE_UNKNOWNを渡す

アダプタを指定せず、デフォルトのGPUを使用するデバイスを作成する場合、D3D11CreateDeviceの第2引数をD3D_DRIVER_TYPE_HARDWAREと設定します。しかし、アダプタを指定してデバイスを生成する場合は、この引数だとエラーになります。そのため、第2引数をD3D_DRIVER_TYPE_UNKNOWNにする必要があります。

まとめ

共有テクスチャを受け取る送信先のプロセス側を修正することで、優先するGPUの設定に依存せず、共有テクスチャのやり取りができるようになります。

IDXGIFactory1EnumAdapters1メソッドによる走査は使用可能なGPUの情報を集めるのにも使用でき、環境によらずDirectXを使用するプログラムを動作させるためには、このような確認も考慮に入れる必要がありそうです。

  1. NVIDIAコントロールパネルとかで一括で優先するGPUを設定しても、アプリケーションごとに優先するGPUが異なるケースもあり、ユーザに設定を見直すように指示するのは厳しいと思います。

  2. これまで書いてきた仮想カメラやウィンドウキャプチャの知見をまとめた集大成として開発し、2025年1月ごろにリリースしました。散らかってしまった知見をまとめたGitHub wikiも書いてみたので、ぜひご覧ください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?