UE4でウィンドウキャプチャー機能を作ってみた


WindowCapture2D

今回UnrealEngineで使えるウィンドウキャプチャープラグインをGitHub上に公開しました。

今はGitHubでのみ公開していますが、もう少し機能がまとまり次第マーケットプレイスにも出そうと思っています。

WindowCaptureへの思い

ある時にVRコンテンツをUE4でWindows上のアプリケーションをVR空間に表示したくて色々調べてたのですが、中々ダイレクトでマッチする機能は探せず。

当時はそんなに時間もなかったのでUE4で実現するのをあきらめていました。

そんな中ネットを見ているとUnityならドンピシャの機能を作られている方がいて、試してみると普通にやりたい事ができてしまい感動した記憶があります。

Unity で Windows のデスクトップ画面をテクスチャとして表示するプラグインを作ってみた - 凹みTips

凄い凄いと思いながら、Unityでも実現できてるんだからUnrealでも作れるんじゃないだろうかと思い、トライしてみたのが始まりです。

Windowをキャプチャーする

私は25歳の時に今で言う「未経験からエンジニア」で転職したのですが、その時最初にやった業務がWindows上でのウィンドウキャプチャーでした。

C++, MFCで戦っていたのですが、当時はHDC? HWND?何それ?メモリDCってなに?って感じだったのを覚えています。

そのためウィンドウをキャプチャーするという技術には特別な思いがあります。

キャプチャー方法

私の知っているウィンドウキャプチャー方法は以下の2つです。

速度的にはBitBltの方がPrintWindowよりも早くて良いのですが、ChromeやUE4で作ったアプリケーションなどはBitBltではキャプチャーできません。

Windowを直接キャプチャーするのではなく、デスクトップのDCからキャプチャーして必要な領域をトリミングすると、どんなウィンドウでもキャプチャーできるのですが、 キャプチャーするのにディスプレイモニターのリフレッシュレート分待たされてしまう問題や、重なったウィンドウの背面側はキャプチャーできない等問題も数多くありました。

もう一つのPrintWindowも当初はChromeのキャプチャーはできないと思っていたのですが、Microsoftのヘルプに載っていない引数(PW_RENDERFULLCONTENT=2)を与えるとキャプチャーできるようになる発見がありました。

そこで今回はPrintWindowで作ってみる事にしました。

(その他にDirectXを使ってウィンドウキャプチャーする方法もあるそうですが、これはまだ試していません)

UE4での実装

とりあえずTickの中でターゲットのウィンドウをキャプチャーしてテクスチャの中身を書き換えてみました。

テクスチャー(UTexture2D)の書き換えは以下のコードで実現できます。

auto Region = new FUpdateTextureRegion2D(0, 0, 0, 0, TextureTarget->GetSizeX(), TextureTarget->GetSizeY());
TextureTarget->UpdateTextureRegions(0, 1, Region, 4 * TextureTarget->GetSizeX(), 4, (uint8*)m_BitmapBuffer);

TextureTargetがターゲットとなるTexture2Dで、 m_BitmapBufferはBGRA形式のピクセルバッファです。

この2行で動的にテクスチャーの書き換えができます。

実行するとうまくいったかのように見えたのですが、対象のウィンドウのサイズが大きくなるとカクツキが酷くなってしまい困りました。

そこで次はキャプチャーの部分のみTaskを使ってバックグラウンドで処理してみました。

void ACaptureMachine::Tick()
{
    AsyncTask(ENamedThreads::BackgroundThreadPriority, [this]() {
        // キャプチャー処理を実施
        });
}

そうすると先ほどよりもフレームレートが上がり効果がありましたので、次に複数ウィンドウのキャプチャーにトライしました。

3枚のウィンドウを同時にキャプチャーしたところ、またまた極端にフレームレートが落ちてしまい困ります。

考えてみると、このウィンドウキャプチャーは1個のActorで1つのウィンドウをキャプチャーしており、Tick内で処理している以上直列に処理が繋がってしまいどんどん遅くなっているのではと仮説をたてました。

なのでTick内で処理するのはやめて、Actor内でスレッドを生成しスレッド内でキャプチャーをするように改造しました。

すると。。。

ちょいミスもありましたが、3枚のウィンドウをキャプチャーしても60fpsキープできるようになりました。

もっとウィンドウの枚数を増やしたり、4Kウィンドウフルサイズなどウィンドウを大きくしていくと負荷は高まっていくとは思いますが、とりあえずは最低限の機能は実装できたかなと思っています。

使い方

使い方は以下の通りです。(今後は仕様変更により使い方が変わる可能性はあります)

Title Matching Window Search

以上でPlayすると設定がうまくいけばウィンドウがキャプチャーされます。

ただし以下の点にはお気を付けください。

※1 現状ウィンドウの探索は起動直後に1回のみのため、対象のウィンドウが表示されている状態でUE4側をPlayする必要があります

※2 対象のウィンドウを最小化してしまうとキャプチャーできなくなります

今後の展開

現状ウィンドウをキャプチャーすることはできるようになりましたが、表示のみで操作ができません。

例えばVRで使うことを考えたときに、空間内でウィンドウを操作できた方が使い道が広がると思うので、是非ウィンドウの操作(マウスやキーボード入力の転送)も実装してみたいと思います。

あとはネットワーク共有とかもやってみたいけど、これは難しそうなので断念するかもです。


See also