Skip to content

SharedTexture

HexagramNM edited this page Feb 16, 2025 · 2 revisions

DirectXの共有テクスチャ

NM_WindowCaputureVirtualCameraではキャプチャされたウィンドウの画像を仮想カメラに送るのに、DirectXの共有テクスチャを利用しています。

プロセス間でテクスチャの共有をすることができますが、他のプロセス間通信と比較して以下のようなメリットがあります。

  • セッション0のプロセスに対してもテクスチャを共有できる

    • セッション0は主にWindowsのサービスを動かしており、ユーザの操作とは分離されたところで動いています。セッション0ではLocalServiceという権限の弱いアカウントで動いており、権限のためにファイルを操作できないこともあります。

    • MediaFoundationの仮想カメラはFrameServerというサービス上で動作するため、セッション0で動くことになります。

  • 通常のプロセスの権限でテクスチャを共有できる

    • 共有テクスチャを作成したり、受信したりするプロセスに特権は必要ありません。

テクスチャのフラグ設定

//他プロセスと共有するためのテクスチャ 
D3D11_TEXTURE2D_DESC bufferTextureDesc;

bufferTextureDesc.Width = VCAM_VIDEO_WIDTH;
bufferTextureDesc.Height = VCAM_VIDEO_HEIGHT;
bufferTextureDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
bufferTextureDesc.ArraySize = 1;
bufferTextureDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
bufferTextureDesc.CPUAccessFlags = 0;
bufferTextureDesc.MipLevels = 1;
bufferTextureDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_NTHANDLE | D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
bufferTextureDesc.SampleDesc.Count = 1;
bufferTextureDesc.SampleDesc.Quality = 0;
bufferTextureDesc.Usage = D3D11_USAGE_DEFAULT;

check_hresult(_dxDevice->CreateTexture2D(&bufferTextureDesc, 0, _sharedCaptureWindowTexture.put()));

テクスチャのフラグを設定する際は、特に以下のフラグ設定が必要です。

  • CPUAccessFlags: 0に設定(CPUからアクセスできないようにする必要があります)
  • MiscFlags: D3D11_RESOURCE_MISC_SHARED_NTHANDLE | D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEXに設定
  • Usage: D3D11_USAGE_DEFAULTに設定

共有ハンドルの作成

com_ptr<IDXGIResource1> sharedCaptureWindowResource;
_sharedCaptureWindowTexture->QueryInterface(IID_PPV_ARGS(sharedCaptureWindowResource.put()));

// Session0であるFrameServerで共有テクスチャにアクセスできるようにするため、
// SECURITY_ATTRIBUTESを設定する必要がある。
SECURITY_ATTRIBUTES secAttr;
secAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
secAttr.bInheritHandle = FALSE;
secAttr.lpSecurityDescriptor = NULL;

// Local Serviceにのみ読み取りのアクセス権(今回のFrameServerのパイプラインならこれで十分)
// Interactive User(現在のユーザ)に全アクセス権を付与するなら(A;;GA;;;IU)を追加する。
if (ConvertStringSecurityDescriptorToSecurityDescriptor(
    TEXT("D:(A;;GR;;;LS)(A;;GR;;;IU)"),
    SDDL_REVISION_1, &(secAttr.lpSecurityDescriptor), NULL)) 
{
    HRESULT hr = sharedCaptureWindowResource->CreateSharedHandle(&secAttr,
        DXGI_SHARED_RESOURCE_READ,
        SHARED_CAPTURE_WINDOW_TEXTURE_PATH, // (L"Global\\NM_WCVCam_Captured_Window")
        &_sharedCaptureWindowHandle);
    com_ptr<IDXGIKeyedMutex> mutex;
    _sharedCaptureWindowTexture.as(mutex);
    mutex->AcquireSync(0, INFINITE);
    mutex->ReleaseSync(MUTEX_KEY);
}

テクスチャを他プロセスで共有するためにはIDXGIResource1CreateSharedHandleメソッドを呼び出し、共有ハンドルを作成する必要があります。その際にハンドルにつける名前と権限に注意が必要です。

権限については、LocalServiceが動かすプロセスでテクスチャの読み取りができるように、SECURITY_ATTRIBUTESの設定が必要です。設定されたものをCreateSharedHandleの引数に渡す必要があります。

名前については、セッション0で動作するプロセスで読み取りができるように、名前の先頭にGlobal\をつける必要があります。

また、この後の排他処理で使用するIDXGIKeyedMutexの鍵となる値を設定しておきます。最初は鍵の値が0となっているので、AcquireSyncに0を引数として渡し、ReleaseSyncを呼び出す際に設定する鍵の値を引数に渡します。

共有テクスチャの取得

_dxDevice->OpenSharedResourceByName(
    SHARED_CAPTURE_WINDOW_TEXTURE_PATH, // (L"Global\\NM_WCVCam_Captured_Window")
    DXGI_SHARED_RESOURCE_READ,
    IID_PPV_ARGS(_sharedCaptureWindowTexture.put()));

共有されたテクスチャはDirectXのデバイス(ID3D11Device)にあるOpenSharedResourceByNameから取得することができます。

プロセス間の排他処理

com_ptr<IDXGIKeyedMutex> mutex;
_sharedCaptureWindowTexture.as(mutex);

// 引数の1つ目に鍵となる値を、2つ目にタイムアウトとなる時間の値を渡します。
// 2つ目の引数にINFINITEを渡すことで、他プロセスがテクスチャの使用を終えるまで待機します。
mutex->AcquireSync(MUTEX_KEY, INFINITE);

// 描画など共有テクスチャを使用する処理

mutex->ReleaseSync(MUTEX_KEY);

IDXGIKeyedMutex インターフェイス (dxgi.h)

IDXGIKeyedMutexAcquireSyncメソッドとReleaseSyncメソッドの間は他プロセスがテクスチャにアクセスしないことが保証されます。ReleaseSyncに渡された値は、次にテクスチャにアクセスしようとAcquireSyncを呼び出す際に必要となる鍵の値となります。プロセスごとに鍵となる値を変えることで呼び出される順番の制御もできるようですが、単に排他処理を行えれば良いので、同じ値を鍵に使用しています。


戻る

Clone this wiki locally