ObjectDelivererの共有メモリを使った映像同期


サンプル解説

今回の記事は昨日書きました共有メモリを使ったサンプルの実装解説記事です。

WPFアプリ側

WPF側は自分のウィンドウの表示内容を共有メモリに書き込むことをやっています。

全ソースは以下のリポジトリにあります。

共有メモリの作成

“SharedMemory"という名前で共有メモリを作成し、メモリサイズは800 * 450 * 4 + 5にしています。 これは横800pixel, 縦450pixel、32bit BGRAのビットマップが格納できるサイズです。 最後の+5はObjectDelivererの共有メモリで使用するヘッダ要素分になります。

private MemoryMappedFile share_mem;
private MemoryMappedViewAccessor accessor;

share_mem = MemoryMappedFile.CreateOrOpen("SharedMemory", 800 * 450 * 4 + 5);
accessor = share_mem.CreateViewAccessor();

WPFのフレーム描画タイミングの仕掛け

CompositionTarget.Renderingイベントを監視することで、毎描画フレームに処理ができるようにします。

CompositionTarget.Rendering += CompositionTarget_Rendering;

表示内容をビットマップに書き込み

Rootというのは表示内容を取得するコントロールにつけたNameです。 まず、VisualTreeHelper.GetDescendantBoundsで表示領域の大きさを取得し、その大きさあったRenderTargetBitmapを作成します。

次にDrawingVisualを作成してからビットマップに表示内容を書き込みます。 書き込みが完了したらビットマップのピクセルバッファの中身をbyteの配列にコピーします。 このbyteの配列が今回欲しいものです。

var visual = Root;
var bounds = VisualTreeHelper.GetDescendantBounds(visual);
var bitmap = new RenderTargetBitmap(
            (int)bounds.Width,
            (int)bounds.Height,
            96.0,
            96.0,
            PixelFormats.Pbgra32);

var dv = new DrawingVisual();
using (var dc = dv.RenderOpen())
{
      var vb = new VisualBrush(visual);
      dc.DrawRectangle(vb, null, bounds);
}

bitmap.Render(dv);
bitmap.Freeze();

var buffer = new byte[800 * 450 * 4];
bitmap.CopyPixels(buffer, 800 * 4, 0);

共有メモリへの書き込み

最後に共有メモリへの書き込みを行います。

まずObjectDelivererのフォーマットに従うために、1バイトのカウンターを書き込みます。 ObjectDelivererはこのカウンタの数字が変わった場合のみ、次に続く中身の読み込みを行いますので書き込む度にカウントアップするようにしています。

カウンタの次はピクセルバッファのサイズを書き込みます。

次にピクセルバッファの中身をコピーして完了です。

上記の書き込みを毎フレーム行います。

// write counter
accessor.Write(0, counter++);

// write buffer size
accessor.Write(1, buffer.Length);

// write buffer
accessor.WriteArray(5, buffer, 0, buffer.Length);

UE4側

UE4側はObjectDelivererを使って共有メモリからピクセルバッファを読み込み、その中身使ってテクスチャを更新します。

Textureヘルパークラスの作成

必要な機能の準備としてC++でUTextureUtilクラスを作成します。

これには以下の2つの関数を実装します。

UFUNCTION(BlueprintPure, Category = "ObjectDeliverer Test")
static UTexture2D* CreateTexture(int32 Width, int32 Height);

UFUNCTION(BlueprintCallable, Category = "ObjectDeliverer Test")
static void UpdateTexture(UTexture2D* Texture, const TArray<uint8>& PixelsBuffer);

UTexture2D* UTextureUtil::CreateTexture(int32 Width, int32 Height)
{
    auto Texture = UTexture2D::CreateTransient(Width, Height, PF_B8G8R8A8);
    Texture->UpdateResource();

    return Texture;
}

void UTextureUtil::UpdateTexture(UTexture2D* Texture, const TArray<uint8>& PixelsBuffer)
{
    auto Region = new FUpdateTextureRegion2D(0, 0, 0, 0, Texture->GetSizeX(), Texture->GetSizeY());
    Texture->UpdateTextureRegions(0, 1, Region, 4 * Texture->GetSizeX(), 4, (uint8*)PixelsBuffer.GetData());
}

CreateTextureは指定したサイズのテクスチャを作成する関数。 UpdateTextureは渡されたテクスチャの中身にピクセルバッファを書き込む関数です。

リアルタイム更新するマテリアルの作成

以下のような単純なマテリアルを用意。 TextureSampleParameter2Dのパラメータ名を"Tex"にしてBase Colorに差し込むだけ。

/images/2019/01/30/233143/20190130231141.png

Actorの作成

空のActorを作成して、中身にCubeを配置。

/images/2019/01/30/233143/20190130231706.png

Actorへのロジック実装

BeginPlayに順番につなげていきます。

テクスチャの作成

事前に作成済みのUTextureUtil::CreateTextureを使ってテクスチャを作成します。

テクスチャのサイズはWPFのビットマップサイズに合わせて800x450にします。

/images/2019/01/30/233143/20190130232004.png

Dynamic Materialの作成とテクスチャのセット

CubeにDynamic Materialを作成して、先ほど作成したテクスチャをマテリアルのTexにセットします。

これでテクスチャの表示がマテリアルに反映されるようになりました。

/images/2019/01/30/233143/20190130232145.png

ObjectDelivererの受信イベント登録

ObjectDelivererManagerのインスタンスを作成して、ReceiveDataイベントを監視します。

イベントが発生したら、先ほど作成したテクスチャの中身をUTextureUtil::UpdateTextureを利用して更新します。

/images/2019/01/30/233143/20190130232351.png

ObjectDelivererの開始

SharedMemory Protocolを差し込んでStartします。

SharedMemory にはWPFで作成した共有メモリと同じ名前をしてして、サイズにはビットマップサイズ(800x450x4=1440000byte)を指定します。

ここのサイズにはヘッダの5byteは含めません。

/images/2019/01/30/233143/20190130232550.png

Blueprint全体

上記Blueprintをつなげると以下のようになります。

/images/2019/01/30/233143/20190130232902.png

できあがり

以上の実装を行ってWPFのアプリケーションとUE4のアプリケーション両方立ち上げると、WPFウィンドウに表示されている内容が、UE4のCube表面に表示されます。


See also