以前書いた記事:DirectXでテクスチャを別プロセスに共有するの補足記事です。
N.Mです。以前の記事でDirectXのテクスチャを共有した際、注意点として以下のことを挙げました。
それはそうと思うかもしれませんが、送信元プロセスと送信先プロセスで同じGPUを使用している必要があります。違うGPUを使用している場合は、送信先のプロセスで
OpenSharedResourceByName
を呼び出したところで、「引数が不正である」(E_INVALIDARG
)といったエラーが返ってきます。
複数のGPUを積んでいるPCで起こりうる問題ですが、実際のところWindows側で優先するGPUを自動選択する設定もできるので、この部分を対策しないと一部の環境に対応できないことがわかりました。1
ウィンドウをキャプチャし、仮想カメラに送る自作ツール: NM_WindowCaptureVirtualCamera2でも修正対応を行ったので、その対応を紹介したいと思います。
対応
共有テクスチャを受け取る送信先のプロセスでDirectXのデバイスを作成する際に、以下を行いました。
- 一度使用可能なすべてのGPUに対してデバイスを走査する
- デバイスを一つずつ見ていき、共有テクスチャの取得に成功するか確認する。取得に成功したデバイスを以降の処理で使用するようにする
共有テクスチャを作る送信元のプロセスは、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のデバイスはIDXGIFactory1
のEnumAdapters1
メソッドで走査できます。IDXGIFactory1
はCreateDXGIFactory1
でそのインスタンスを作成できます。
EnumAdapters1
は第1引数で渡すインデックスに対応したアダプタIDXGIAdapter1
を取得できます。インデックスを0から順に1ずつ増やすことで、使用可能なGPUに紐づいたアダプタすべてを走査できます。走査が完了するとEnumAdapters1
はDXGI_ERROR_NOT_FOUND
を返します。
ここで取得されたIDXGIAdapter1
はD3D11CreateDevice
の第1引数に渡すことができ、渡すことでアダプタに紐づいたGPUを使用するDirectXのデバイス(ID3D11Device
)を作成できます。このように作成されたDirectXのデバイスは、一度QueryInterface
でID3D11Device1
に変換し、OpenSharedResourceByName
メソッドで共有テクスチャの取得を試みます。
エラーが発生せずに取得できればwhile文を抜け、その時点で作成、取得したデバイスやデバイスコンテキスト、共有テクスチャを以降の処理で使用するようにします。エラーが発生した場合はインデックスを更新し、continueで次のアダプタに対して同様の確認を行います。
注意点
最後に1のつくIDXGIFactory1
やEnumAdapters1
などを使用する
似たものとしてIDXGIFactory
やEnumAdapters
といったものがあります。これらはDXGI 1.0の機能を使用したものです。共有テクスチャはDXGI 1.1から使用できるようになった機能なので、IDXGIFactory
やEnumAdapters
を使用すると、共有テクスチャ使用時に必ずエラーが発生します。
そのため、DXGI 1.1に対応したIDXGIFactory1
やEnumAdapters1
などを使用する必要があります。
D3D11CreateDevice
の引数にD3D_DRIVER_TYPE_UNKNOWN
を渡す
アダプタを指定せず、デフォルトのGPUを使用するデバイスを作成する場合、D3D11CreateDevice
の第2引数をD3D_DRIVER_TYPE_HARDWARE
と設定します。しかし、アダプタを指定してデバイスを生成する場合は、この引数だとエラーになります。そのため、第2引数をD3D_DRIVER_TYPE_UNKNOWN
にする必要があります。
まとめ
共有テクスチャを受け取る送信先のプロセス側を修正することで、優先するGPUの設定に依存せず、共有テクスチャのやり取りができるようになります。
IDXGIFactory1
のEnumAdapters1
メソッドによる走査は使用可能なGPUの情報を集めるのにも使用でき、環境によらずDirectXを使用するプログラムを動作させるためには、このような確認も考慮に入れる必要がありそうです。
-
NVIDIAコントロールパネルとかで一括で優先するGPUを設定しても、アプリケーションごとに優先するGPUが異なるケースもあり、ユーザに設定を見直すように指示するのは厳しいと思います。 ↩
-
これまで書いてきた仮想カメラやウィンドウキャプチャの知見をまとめた集大成として開発し、2025年1月ごろにリリースしました。散らかってしまった知見をまとめたGitHub wikiも書いてみたので、ぜひご覧ください。 ↩