GetDXDeviceWithSharedTexture - HexagramNM/NM_WindowCaptureVirtualCamera GitHub Wiki
ゲーミングノートPCのように、複数のGPUをもつケースがあります。この場合、DirectXの共有テクスチャを送るプロセスと受け取るプロセスでそれぞれ異なるGPU(DirectXデバイス)を使用していると、共有テクスチャの受け渡しに失敗します。さらに、プロセスが優先的に使用するGPUはWindowsで自動選択するよう設定もでき、単純にデフォルトのGPUを使用すれば良いというわけではありません。
NM_WindowCaputureVirtualCameraでは、共有テクスチャを送るGPUアプリケーション側のプロセスではデフォルトのGPUを使用するようにし、共有テクスチャを受け取る仮想カメラ側のプロセスでは、共有テクスチャを受け取ることのできるDirectXデバイスを探すようにしています。
以下の記事をヒントにDirectXのデバイスを探す処理を書いています。
コードの詳細(変数の型など)についてはNM_WCVCam_DS/NMVCamFilter.h
やNM_WCVCam_DS/NMVCamPin.cpp
をご覧ください。
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
の機能を使用することで列挙できます。最初にCreateDXGIFactory1
でIDXGIFactory1
のインスタンスを作成し、そのインスタンスのEnumAdapters1
メソッドを呼び出します。EnumAdapters1
は第1引数で渡すインデックスに対応したアダプタIDXGIAdapter1
を取得できます。インデックスを0から順に1ずつ値を増やすことで、使用可能なGPUに紐づいたアダプタすべてを走査できます。走査が完了すると最終的にEnumAdapters1
メソッドはDXGI_ERROR_NOT_FOUND
を返します。
このIDXGIAdapter1
はD3D11CreateDevice
に渡すことで、アダプタに紐づいたGPUを使用するDirectXのデバイスを取得できます。各アダプタで取得されたDirectXのデバイスでOpenSharedResourceByName
を呼び出し、共有テクスチャの取得を試みます。エラーが発生せず、共有テクスチャを取得できた場合は、while文を抜けて、共有テクスチャを取得できたDirextXのデバイスを使用するようにしています。
共有テクスチャは、DirectXのデバイスのOpenSharedResourceByName
メソッドで取得できます。詳しくはこちらをご覧ください。
Warning
似たようなものとしてIDXGIFactory
やEnumAdapters
などがありますが、これはDXGI 1.0の機能を使用したものです。共有テクスチャはDXGI 1.1から使用できるようになったので、最後に1がついていないものを使用してしまうと、共有テクスチャを取得する際に必ずエラーが出てしまいます。必ず最後に1がついているIDXGIFactory1
やEnumAdapters1
などを使用してください。
ID3D11Adapter1
をD3D11CreateDevice
に渡しDirectXのデバイスを取得する際、D3D11CreateDevice
の第2引数はD3D_DRIVER_TYPE_UNKNOWN
にしておく必要があります。(アダプタを指定しない場合は、これまでD3D_DRIVER_TYPE_HARDWARE
にしていましたが、これだとエラーが出ます。)
// ハンドルから共有されたテクスチャを取得
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側で停止された際など、共有テクスチャを取得出来なくなった場合は、_sharedCaptureWindowTexture
をnullptr
にし、共有テクスチャが見つかっていない状態に戻します。