GetDXDeviceWithSharedTexture - HexagramNM/NM_WindowCaptureVirtualCamera GitHub Wiki

適切なGPU(DirectXデバイス)から共有テクスチャを取得する

ゲーミングノートPCのように、複数のGPUをもつケースがあります。この場合、DirectXの共有テクスチャを送るプロセスと受け取るプロセスでそれぞれ異なるGPU(DirectXデバイス)を使用していると、共有テクスチャの受け渡しに失敗します。さらに、プロセスが優先的に使用するGPUはWindowsで自動選択するよう設定もでき、単純にデフォルトのGPUを使用すれば良いというわけではありません。

NM_WindowCaputureVirtualCameraでは、共有テクスチャを送るGPUアプリケーション側のプロセスではデフォルトのGPUを使用するようにし、共有テクスチャを受け取る仮想カメラ側のプロセスでは、共有テクスチャを受け取ることのできるDirectXデバイスを探すようにしています。

以下の記事をヒントにDirectXのデバイスを探す処理を書いています。

コードの詳細(変数の型など)についてはNM_WCVCam_DS/NMVCamFilter.hNM_WCVCam_DS/NMVCamPin.cppをご覧ください。

DirectXデバイスの検索

void NMVCamPin::FindDeviceAndGetSharedTexture(ID3D11Texture2D** 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, _dxDeviceContext.put());
        if (hr != S_OK)
        {
            adapterIdx++;
            continue;
        }

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

        // 作成されたデバイスで共有テクスチャの取得を試み、成功した場合はそのDirectXのデバイスを以降使用する
        hr = _dxDevice->OpenSharedResourceByName(SHARED_CAPTURE_WINDOW_TEXTURE_PATH,
            DXGI_SHARED_RESOURCE_READ, IID_PPV_ARGS(sharedTexture));
        if (hr != S_OK)
        {
            adapterIdx++;
            continue;
        }

        break;
    }
}

DirectXのデバイスはIDXGIFactory1の機能を使用することで列挙できます。最初にCreateDXGIFactory1IDXGIFactory1のインスタンスを作成し、そのインスタンスのEnumAdapters1メソッドを呼び出します。EnumAdapters1は第1引数で渡すインデックスに対応したアダプタIDXGIAdapter1を取得できます。インデックスを0から順に1ずつ値を増やすことで、使用可能なGPUに紐づいたアダプタすべてを走査できます。走査が完了すると最終的にEnumAdapters1メソッドはDXGI_ERROR_NOT_FOUNDを返します。

このIDXGIAdapter1D3D11CreateDeviceに渡すことで、アダプタに紐づいたGPUを使用するDirectXのデバイスを取得できます。各アダプタで取得されたDirectXのデバイスでOpenSharedResourceByNameを呼び出し、共有テクスチャの取得を試みます。エラーが発生せず、共有テクスチャを取得できた場合は、while文を抜けて、共有テクスチャを取得できたDirextXのデバイスを使用するようにしています。

共有テクスチャは、DirectXのデバイスのOpenSharedResourceByNameメソッドで取得できます。詳しくはこちらをご覧ください。

Warning

似たようなものとしてIDXGIFactoryEnumAdaptersなどがありますが、これはDXGI 1.0の機能を使用したものです。共有テクスチャはDXGI 1.1から使用できるようになったので、最後に1がついていないものを使用してしまうと、共有テクスチャを取得する際に必ずエラーが出てしまいます。必ず最後に1がついているIDXGIFactory1EnumAdapters1などを使用してください。

ID3D11Adapter1D3D11CreateDeviceに渡しDirectXのデバイスを取得する際、D3D11CreateDeviceの第2引数はD3D_DRIVER_TYPE_UNKNOWNにしておく必要があります。(アダプタを指定しない場合は、これまでD3D_DRIVER_TYPE_HARDWAREにしていましたが、これだとエラーが出ます。)

DirectXのデバイスが見つかった後

// ハンドルから共有されたテクスチャを取得
void NMVCamPin::GetSharedTextureFromHandle()
{
    winrt::com_ptr<ID3D11Texture2D> tempCaptureWindowTexture = nullptr;
    if (_sharedCaptureWindowTexture == nullptr) {
        // 共有テクスチャが見つかってない状態の時は、共有テクスチャを取得できるDirectXのデバイスを検索する
        FindDeviceAndGetSharedTexture(tempCaptureWindowTexture.put());
    }
    else {
        // 共有テクスチャが見つかっている場合は、見つけたDirectXから引き続き共有テクスチャを取得する
        _dxDevice->OpenSharedResourceByName(SHARED_CAPTURE_WINDOW_TEXTURE_PATH,
            DXGI_SHARED_RESOURCE_READ, IID_PPV_ARGS(tempCaptureWindowTexture.put()));
    }

    if (tempCaptureWindowTexture == nullptr)
    {
        // 上の共有テクスチャ取得でテクスチャが取得できない場合は、共有テクスチャが見つかっていない状態にする
        _sharedCaptureWindowTexture = nullptr;
    }
    else
    {
        if (_sharedCaptureWindowTexture == nullptr)
        {
            // 共有テクスチャが見つかっていない状態から、共有テクスチャを取得できた場合は、
            // DirectXのデバイスも取得できているため、初回だけその他DirectX関連の設定を行う
            _sharedCaptureWindowTexture = tempCaptureWindowTexture;
            SetupSampleFormatter();
        }

        // ここのmutexはGetSampleOnCaptureWindowでReleaseしている。
        winrt::com_ptr<IDXGIKeyedMutex> mutex;
        _sharedCaptureWindowTexture->QueryInterface(IID_PPV_ARGS(mutex.put()));
        if (mutex != nullptr)
        {
            mutex->AcquireSync(MUTEX_KEY, INFINITE);
        }

        CD3D11_SHADER_RESOURCE_VIEW_DESC shaderResourceViewDesc(D3D11_SRV_DIMENSION_TEXTURE2D, DXGI_FORMAT_B8G8R8A8_UNORM);
        _dxDevice->CreateShaderResourceView(_sharedCaptureWindowTexture.get(),
            &shaderResourceViewDesc, _formatterSRV.put());
    }
}

DirectXのデバイスが取得できた後は、コンピュートシェーダなどをそのデバイスやデバイスコンテキストに設定する必要があります。そのため、共有テクスチャが見つかっていない状態から共有テクスチャを取得できた初回のみSetupSampleFormatterメソッドを呼び出し、コンピュートシェーダなどの設定をしています。

一度共有テクスチャを取得できた後は、同じDirectXのデバイスから共有テクスチャの取得を試みます。仮想カメラをGUI側で停止された際など、共有テクスチャを取得出来なくなった場合は、_sharedCaptureWindowTexturenullptrにし、共有テクスチャが見つかっていない状態に戻します。


戻る

⚠️ **GitHub.com Fallback** ⚠️