WinRT_WindowsGraphicsCapture - HexagramNM/NM_WindowCaptureVirtualCamera GitHub Wiki
WinRTのWindows.Graphics.Captureではハードウェアアクセラレーションの機能でGPUで描画処理を行っているウィンドウをキャプチャし、DirectXのテクスチャという形で取得することができます。
以下の流れでキャプチャされたウィンドウを取得できます。
- DirectXのデバイスである
IDirect3DDevice
を作成 - ウィンドウピッカーの結果もしくはウィンドウハンドルからキャプチャ対象のウィンドウ情報をもつ
GraphicsCaptureItem
を取得 -
IDirect3DDevice
とGraphicsCaptureItem
からDirect3D11CaptureFramePool
を作成 -
Direct3D11CaptureFramePool
のイベントFrameArrived
で、引数からDirectXのテクスチャに変換する。
UINT createDeviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
D3D_FEATURE_LEVEL d3dFeatures[1] =
{
D3D_FEATURE_LEVEL_11_1
};
// _dxDevice: com_ptr<ID3D11Device>
// _dxDeviceContext: com_ptr<ID3D11DeviceContext>
D3D_FEATURE_LEVEL resultFeature;
check_hresult(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE,
nullptr, createDeviceFlags, d3dFeatures, 1, D3D11_SDK_VERSION,
_dxDevice.put(), &resultFeature, _dxDeviceContext.put()));
com_ptr<IDXGIDevice> dxgiDevice = _dxDevice.as<IDXGIDevice>();
com_ptr<::IInspectable> device = nullptr;
check_hresult(::CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.get(), device.put()));
// _dxDeviceForCapture: IDirect3DDevice
_dxDeviceForCapture = device.as<winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice>();
DirectXの開発では、テクスチャといったリソースの管理などを行うデバイス(ID3D11Device
)と描画命令を管理するデバイスコンテキスト(ID3D11DeviceContext
)を最初に作成します。この作成はD3D11CreateDevice
関数で行うことが可能です。デバイスコンテキストはWindowsCaptureAPIでは使用しませんが、この後のオフスクリーンレンダリングの描画で使用します。
さらに、DirectXのデバイスをWindowsCaptureAPIで使用できるように、CreateDirect3D11DeviceFromDXGIDevice
関数を使ってIDirect3DDevice
に変換します。
GraphicsCaptureItem
はウィンドウピッカーから取得したり、ウィンドウハンドルから直接作成したりできます。
winrt::Windows::Foundation::IAsyncAction NM_WindowCapture::OpenWindowPicker()
{
init_apartment(winrt::apartment_type::single_threaded);
SetWindowDisplayAffinity(_baseHwnd, WDA_EXCLUDEFROMCAPTURE);
GraphicsCapturePicker picker;
auto interop = picker.as<::IInitializeWithWindow>();
interop->Initialize(_baseHwnd);
GraphicsCaptureItem pickerResult = co_await picker.PickSingleItemAsync();
if (pickerResult != nullptr)
{
_graphicsCaptureItem = pickerResult;
}
SetWindowDisplayAffinity(_baseHwnd, WDA_NONE);
ChangeWindow();
}
WindowCaptureAPIにはウィンドウを選択するピッカーの機能があります。このピッカー機能を利用する場合は最初にinit_apartment(winrt::apartment_type::single_threaded);
を呼び出しておく必要があります。この呼び出しで、WinRTのCOMを初期化し、ピッカーが使える状態になるようです。
このピッカーは他のウィンドウに紐づいている必要があります。NM_WindowCaputureVirtualCameraでは_baseHwnd
のウィンドウハンドルに対応するウィンドウを紐づけています。最初にそのウィンドウをIInitializeWithWindow
のInitialize
でピッカーに紐づけています。
ピッカーの結果はPickSingleItemAsync
で受け取れます。非同期関数であるので、co_await
で結果が来るまで待機することになります。
キャプチャの対象から特定のウィンドウを外すこともできます。SetWindowDisplayAffinity
関数で、ウィンドウにWDA_EXCLUDEFROMCAPTURE
フラグを指定します。こうすることで、ピッカーからもそのウィンドウが除外されます。
Note
ピッカーに紐づけているウィンドウはフォアグラウンドにある状態である必要があります。NM_WindowCaputureVirtualCameraでは、ピッカーを開く処理を起動するためにGUIのボタンを押しているので、すでにフォアグラウンドにあります。コード内で新たにウィンドウを作成する場合は、デフォルトではフォアグラウンドになっていないので、一度最小化してから復元し、フォアグラウンド化する必要があります。
void NM_WindowCapture::SetTargetWindowForCapture(HWND targetWindow)
{
namespace abi = ABI::Windows::Graphics::Capture;
if (targetWindow == NULL)
{
return;
}
auto factory = get_activation_factory<GraphicsCaptureItem>();
auto interop = factory.as<::IGraphicsCaptureItemInterop>();
check_hresult(interop->CreateForWindow(targetWindow, guid_of<abi::IGraphicsCaptureItem>(),
reinterpret_cast<void**>(put_abi(_graphicsCaptureItem))));
ChangeWindow();
}
上記のようにget_activation_factory
でGraphicsCaptureItem
を作るためのファクトリを作成します。対象となるウィンドウハンドルを引数に、このファクトリのCreateForWindow
を呼び出すことで、そのウィンドウを対象にしたGraphicsCaptureItem
を作成することができます。
void NM_WindowCapture::ChangeWindow()
{
if (_graphicsCaptureItem == nullptr)
{
return;
}
// 中略
// FramePoolの作成
// _framePoolForCapture: Direct3D11CaptureFramePool
_capWinSize = _graphicsCaptureItem.Size();
_framePoolForCapture = Direct3D11CaptureFramePool::CreateFreeThreaded(_dxDeviceForCapture,
DirectXPixelFormat::B8G8R8A8UIntNormalized, 2, _capWinSize);
// FrameArrivedイベントの設定
// _frameArrivedForCaptureevent_revoker<IDirect3D11CaptureFramePool>
_frameArrivedForCapture = _framePoolForCapture.FrameArrived(auto_revoke, { this, &NM_WindowCapture::OnFrameArrived });
// CaptureSessionの作成と開始
// _captureSession: GraphicsCaptureSession
_captureSession = _framePoolForCapture.CreateCaptureSession(_graphicsCaptureItem);
//IsCursorCaptureEnabledでカーソルもキャプチャするか指定できる。
_captureSession.IsCursorCaptureEnabled(_captureCursor);
_captureSession.StartCapture();
}
最初にFramePoolをCreateFreeThreaded
で作成します。その際に1で作成したIDirect3DDevice
を引数に渡します。次にそのFramePoolにFrameArrived
イベントを設定します。最後にFramePoolからCaptureSessionを作成し、そのCaptureSessionでのキャプチャを開始します。CaptureSessionの作成時に2で取得、作成したGraphicsCaptureItem
を引数に渡します。
template<typename T>
auto getDXGIInterfaceFromObject(winrt::Windows::Foundation::IInspectable const& object)
{
auto access = object.as<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>();
com_ptr<T> result;
check_hresult(access->GetInterface(guid_of<T>(), result.put_void()));
return result;
}
// 中略
void NM_WindowCapture::OnFrameArrived(Direct3D11CaptureFramePool const& sender,
winrt::Windows::Foundation::IInspectable const& args)
{
// 中略
// キャプチャされるフレームを引数から取得
auto frame = sender.TryGetNextFrame();
// 取得されたフレームから現在のウィンドウのサイズを取得できる。
SizeInt32 itemSize = frame.ContentSize();
if (itemSize.Width <= 0)
{
itemSize.Width = 1;
}
if (itemSize.Height <= 0)
{
itemSize.Height = 1;
}
// ウィンドウがリサイズされた場合、FramePoolのRecreateを呼び出す必要がある。
if (itemSize.Width != _capWinSize.Width
|| itemSize.Height != _capWinSize.Height)
{
_capWinSize = itemSize;
// 中略
_framePoolForCapture.Recreate(_dxDeviceForCapture,
DirectXPixelFormat::B8G8R8A8UIntNormalized, 2, _capWinSize);
}
// 取得されたフレームからキャプチャされた画像を含むテクスチャ(ID3D11Texture2D)を取得できる。
// このテクスチャはDirectXでの描画にも使用できる。
com_ptr<ID3D11Texture2D> currentTexture = getDXGIInterfaceFromObject<::ID3D11Texture2D>(frame.Surface());
// 中略
}
ウィンドウの映像がキャプチャされる度、FrameArrived
イベントに設定されたメソッドが呼び出されます。イベントの引数から現在のウィンドウのサイズやキャプチャされた画像のテクスチャを取得できます。ここで取得されたテクスチャはDirectXの描画に使用できます。
ウィンドウサイズが変化した場合は、FramePoolのRecreate
メソッドを呼び出します。これにより変化したサイズに合わせて、以降のフレームがキャプチャされるようになります。