シネマティックスでVirtualTexturingの事前ロードを行う機能を実装したい

お世話になっております。

<br/>

◆前提

VirtualTexturingのストリーミングの遅れによるテクスチャポッピングを抑制する機能が実装できないか試しています。シネマティックスでのポッピングを抑制したいので、シネマティックスでのVT事前ロードの仕組みを作ろうとしています。

<br/>

◆Cinematic Prestreamingプラグイン

Cinematic Prestreaming プラグインの導入も試しましたが、以下の理由により導入を断念しました

  • シネマティックではあるが、ゲームの進行度やユーザー設定によりキャラクターの見た目が変わることがあり、事前ストリーミングデータを用意するのが困難
  • 作成した事前ストリーミングデータが容量的にゲーム内に入り切らないという懸念

<br/>

導入は断念しましたが、内部の実装を確認して参考になるところがないか探しました。

そこから、`FRenderVirtualTextureTiles::RequestVirtualTextureTiles(…)`を呼び出すことで事前ロードができないか試そうとしました。

<br/>

`FRenderVirtualTextureTiles::RequestVirtualTextureTiles(…)`はいくつかのオーバーロードを持っていますが、いずれも呼び出す側の実装が見当たらず、使い方がよくわかっていない状態です。

<br/>

関連がありそうな、`FVirtualTextureSystem`の実装も確認中ですが、こちらもあまり理解できていません。

<br/>

◆聞きたいこと

  • FRendererModule::RequestVirtualTextureTiles(…) を使えばテクスチャを事前ロードすることが可能と考えて間違いないでしょうか
  • FRendererModule::RequestVirtualTextureTiles(…) の引数はどのように取得すればよいでしょうか?

<br/>

情報に曖昧な部分がありますが、ご容赦ください。ご確認をよろしくお願いします。

お世話になっております。

>FRendererModule::RequestVirtualTextureTiles(…) を使えばテクスチャを事前ロードすることが可能と考えて間違いないでしょうか

はい。厳密にはリクエストを行うだけですが、LoadPendingVirtualTextureTiles を併用することでリクエストされた部分のロードを行うこともできます。

負荷分散のため同時にリクエスト可能な数量に上限があり、1フレームで足りない場合は幾らか時間をかけてリクエストを続けることになるかと思います(Cinematic Prestreaming においても毎フレーム処理される OnAnimate でリクエストを行っています)。

>FRendererModule::RequestVirtualTextureTiles(…) の引数はどのように取得すればよいでしょうか?

・Tex.cpp 891行目~(Tex::TransferVirtualTextureToRT 関数内)

・TextureEditorViewportClient.cpp 324行目~(FTextureEditorViewportClient::Draw 関数内)

に IRendererModule::RequestVirtualTextureTiles を使用している例がありますのでそれらを参考にされるのが良いと思います。

それぞれ異なるオーバーロードを用いていますが、いずれの例においても Texture2D への参照を元にリクエストを行えるようになっています。

実際にリクエストを行う部分が RenderThread で実行される必要がある点にはご注意下さい。

前者は関数そのものが RenderThread で呼び出されることを期待しており、後者では ENQUEUE_RENDER_COMMAND を利用することでこれを満たしています。

よろしくお願いいたします。

お世話になっております。

返事が遅れてしまい、申し訳ありません。いただいた情報を元に、Virtual Texturingの実装について調べていました。

​UEのソースコードを読んでいますが、わからない箇所がいくつかありましたので追加で質問させてください。

◆マテリアル・テクスチャからIAllocatedVirtualTextureは生成可能であるか

>TextureEditorViewportClient.cpp 324行目~(FTextureEditorViewportClient::Draw 関数内)

こちらを参考に、​「RenderModule.RequestVirtualTextureTiles(…)」と「RenderModule.LoadPendingVirtualTextureTiles(…)」呼び出しまでの流れを追っていました。

RenderModule.RequestVirtualTextureTiles()の引数に「IAllocatedVirtualTexture*」が必要なのですが、こちらを取得する手段を追いきれませんでした。「IAllocatedVirtualTexture*」はマテリアルまたはテクスチャから生成可能なものなのでしょうか?

(パッケージのコールスタックも確認し、FMaterialRenderProxy::AllocateVTStack()あたりのコードも調査したのですが、こちらもあまり理解が進みませんでした。)​

◆​CinematicPrestreamingでつかわれるPageIDsについて

​>Cinematic Prestreaming においても毎フレーム処理される OnAnimate でリクエストを行っています)

Cinematic Prestreaming のコードを確認したところ、「FRendererModule::RequestVirtualTextureTiles(TArray<uint64>&& InPageRequests)」を呼び出していました。この引数の「InPageRequests」とは何を表しているのでしょうか?

◆​VirtualPageTableテクスチャについて

VTを使用したマテリアルをレンダリングすると、Gバッファ描き込み時にVirtualPhysicalTextureと同時にVirtualPageTableというテクスチャを参照しています。VirtualPageTableテクスチャはランタイムで生成されているもののようですが、こちらはどういった使われ方をしているのでしょうか?

[Image Removed]​

質問は以上です。​

​お手数をおかけしますが、ご確認をよろしくお願いいたします。

お世話になっております。

ご回答ありがとうございます。

いただいた情報を元に、以下の手順でVirtualTexturingのVirtualPhysicalTextureに任意のテクスチャをロードさせることができました。

1. LoadObject<UTexture>を使ってUTextureを作成

2. UTextureをstatic_castしてFVirtualTexture2DResource*に変換

3. FVirtualTexture2DResource*->AcquireAllocatedVT()でIAllocatedVirtualTexture*を取得

4. RenderModule.RequestVirtualTextureTiles() / RenderModuke.LoadPendingVirtualTextureTiles でVirtualPhysicalTextureにロード

以上を踏まえて、追加の質問をさせてください。

上記の手順で、VirtualPhysicalTextureに任意のテクスチャをロードさせることができることは確認したのですが、マテリアルが描画されるときには再度VTロードが行われてしまいました。(VirtualPhysicalTextureに目的のテクスチャがある状態でも、再びテクスチャがロードされる)

マテリアルを通したテクスチャ参照と、LoadObjectで直接テクスチャを参照した場合だと、内部では別テクスチャとして扱われてしまうのでしょうか?

以上です。お手数をおかけしますが、ご確認をよろしくお願いします。

すみません、追加でもう一つ質問させてください。

CinePrestreamingRecorderSetting::OnEndFrame_RenderThread()

こちらについてですが、こちらのメソッドはフレームのレンダリングが完了した段階で、1フレームごとに1回呼ばれていると考えて間違いないでしょうか?

それとも、GPUのパイプラインを起動(ドローコール/コンピュートシェーダーなど​)するたびに呼ばれる(つまり、1フレーム中複数回呼ばれる)ものでしょうか?

お世話になっております。こちら、調査を続けています。

>アセット一般についてですが、ロード毎に新しく生成されるということはなく同一のオブジェクトを参照します

こちらの情報を元に、LoadObject<UTexture>を使ってUTexture*を作成し、更にIAllocatedVirtualTexture*を取得する場合とマテリアルが参照しているテクスチャから取得されるIAllocatedVirtualTexture*を比較したところ、同一のものであることが確認できました。

しかし、同一のIAllocatedVirtualTexture*が作成されているにもかかわらず、キャッシュが効いていない状態でした。物理プールへロードされていることも確認しましたが、キャッシュが効いていませんでした(一瞬、荒いテクスチャが表示されてしまう)

再掲ですが、VTロードの手順は以下のとおりです。

1. LoadObject<UTexture>を使ってUTextureを作成

2. UTextureをstatic_castしてFVirtualTexture2DResource*に変換

3. FVirtualTexture2DResource*->AcquireAllocatedVT()でIAllocatedVirtualTexture*を取得

4. RenderModule.RequestVirtualTextureTiles() / RenderModuke.LoadPendingVirtualTextureTiles でVirtualPhysicalTextureにロード

キャッシュが効かない原因を探りたいので、キャッシュを使うか否かの判定を行っている処理を調べたいのですが、どこを見ればよいかわからなかったため、情報をいただけると助かります。

よろしくお願いいたします。

ご回答ありがとうございます。

いただいた情報を元に以下のことを試しました。

https://www.docswell.com/s/EpicGamesJapan/51NY7K-UE_CEDEC2022_CitySampleRenderingOptimize#p76

こちらの情報を参考に、VTロード設定を変更しました。値は↑のものと同じにしてみました。

`r.VT.MaxUploadPerFrame`という項目があり、500という数値が設定されています。こちらのプロジェクトでも同じ数値にしてみましたが引き続きテクスチャロードに遅延が発生しました。シーンをお見せすることはできないのですが、ゲームの状況的に500以上のテクスチャのロードが必要なシーンではありません。

試しに`r.VT.MaxUploadPerFrame`の数値を1024,10000のような極端な値にしてみましたが、挙動に違いはないようでした。

`r.VT.MaxUploadPerFrame`のパラメータ以外にも、1フレームあたりのアップロード数を制限するような設定があるのでしょうか?

補足:

↑のスライド資料に乗っているパラメータ(r.VT.Max~)の値を色々と変更してみましたが、1フレームあたりのアップロード数を制限を緩和することはできませんでした。

お世話になっております。

頂いた情報を元に検証を進めていました。まだ問題の解決には至っていませんが、現状を共有いたします。

◆ページIDの一致

ランタイム中にPageIDsを取得して厳密することはできませんでしたが、ロード方法を修正して事前ロードを行いたいマテリアルと同一のMaterialInterfaceを使うようにしました

具体的には、事前ロードを行いたいSkeletal Meshから一連のMaterialInterfaceを取得して「FRendererModule::RequestVirtualTextureTiles(const FMaterialRenderProxy* InMaterialRenderProxy, const FVector2D& InScreenSpaceSize, ERHIFeatureLevel::Type InFeatureLevel)」を呼び出します。InScreenSpaceSizeについては、いくつか適当なパラメータを与えて試しました。

◆VT結果反映までのパス

>Feedback→集計→反映の1フレーム遅延で頭打ち

こちらが該当しているような挙動が確認されています。シーケンス内のBPトラックでアクター(Skeletal Meshを含んだもの)をスポーンしているのですが、シーケンス再生中にスポーンしているので「Feedback→集計→反映の1フレーム」が必要になっているのかなと。

ただこの場合でも使用するMaterialInterfaceとScreenSizeは固定のはずなので、事前ロードできそうな気もしています。

◆コンソール変数

↑を参考に、以下の値を設定しました

r.vt.FeedbackFactor 8

r.VT.MaxUploadsPerFrame 500

r.VT.MaxUploadsPerFrame.Streaming 100

試してみた対応としては以上です。いずれの施策も効果が見られませんでした(どうしても、一瞬だけ低解像度テクスチャが表示されてしまう)

stat VirtualTexture、r.VT.Residency.Notify 1 についても確認しました。

◆stat VirtualTexture

Num page visible not resident:ave155, max8905

Num page update:ave1743, max10592

程度でした

◆r.VT.Residency.Notify

プールサイズ溢れは確認できませんでした(警告はでなかった)

r.VT.Residency.show でグラフも確認しましたが、常駐は発生していませんでした。

ご確認ありがとうございます。

◆マテリアル・テクスチャからIAllocatedVirtualTextureは生成可能であるか​

最小限を抜粋しますと以下の部分が必要です。お確かめ下さい。

// TextureEditorViewportClient.cpp
// line326
			FVirtualTexture2DResource* VTResource = static_cast<FVirtualTexture2DResource*>(Texture->GetResource());
// line339
				IAllocatedVirtualTexture* AllocatedVT = VTResource->AcquireAllocatedVT();

◆​CinematicPrestreamingでつかわれるPageIDsについて

処理をさかのぼると

UCinePrestreamingRecorderSetting::OnEndFrame_RenderThread 関数内で

uint64 Handle = GetRendererModule().GetVirtualTextureRequestRecordBuffer(PageRequests);として取得したもののようです。

エディタ上でリクエストされた内容を記録しておくことで実行時の事前ストリーミングを可能とするという使い方ですので、キャラクターの外見が変化する等の場合には有効ではない手段ということになるかと思います。

◆​VirtualPageTableテクスチャについて

VirtualPhysicalTexture に配置された実際の(通常のテクスチャに記録されているような)ピクセル値を間接的に参照するためのテーブルになっています。

VirtualTexture について分かりやすく解説されている記事がありましたので、もしまだでしたら記事内のリンク先も含めてご覧いただくことで理解の助けになるかと思います。

https://qiita.com/EGJ-Nori_Shinoyama/items/4a2e84dd1d3448e81bed

よろしくお願いいたします。​

>マテリアルを通したテクスチャ参照と、LoadObjectで直接テクスチャを参照した場合だと、内部では別テクスチャとして扱われてしまうのでしょうか?

アセット一般についてですが、ロード毎に新しく生成されるということはなく同一のオブジェクトを参照します(途中でアンロードされた場合はこの限りではありません)。

>CinePrestreamingRecorderSetting::OnEndFrame_RenderThread()

>こちらについてですが、こちらのメソッドはフレームのレンダリングが完了した段階で、1フレームごとに1回呼ばれていると考えて間違いないでしょうか?

その認識で問題ありません。

UCinePrestreamingRecorderSetting::SetupForPipelineImpl 関数内で FCoreDelegates::OnEndFrameRT にバインドされており、

FEngineLoop::Tick

-> EndFrameRenderThread

-> FCoreDelegates::OnEndFrameRT.Broadcast()

のような流れで呼び出されます。

よろしくお願いいたします。

お世話になっております。

本件急ぎ対応が必要とのことで担当を代わり返信させて頂きます。

今回問題となっているキャッシュ、Virtual Texture(VT)の実際のフェッチ処理に関して一度整理させていただくと、

シェーダー側の読み込み処理としましては、TextureLoadVirtualPageTable()が参考になるかと思います。

UVなどの情報から欲しいmipmapレベルなどを求め、TextureLoadVirtualPageTableInternal()でマッピングされた内容を読み込み、またStoreVirtualTextureFeedback()で次に必要なVTPageの情報を保存(Feedback)しています。

独自で実装頂いたロードの場合、上記処理で最低ミップが使われている、または必要とされているロードが行えていない可能性がありそうです。

VTはBasePassにあるテクスチャサンプル時(上記関数処理)に、UVなどの情報から必要とされるPageやMipレベルをフィードバックしており、基本的な流れとして、描画時に必要とされる内容のフィードバックからリクエストを行うといった、1~フレーム遅れるようなスロットリング処理でロードをすることを想定しています。

そして手動でリクエストを行う場合、どのMipLevel、位置のテクスチャが必要とされているか選択することが難しい問題になるかと思われます。

遅延への対応としましては、UE5.6にてCPUベースでのテクスチャストリーミングとハイブリットで行い、テクスチャにPrefetch設定を追加したCL42431812の対応がございます。

こちら変更が多くなっておりUE5.4に取り込むには難しい可能性がございますが、実装としては関連してくるかと思いますので必要に応じてご確認いただけますと幸いです。

---

上記は機能追加に向けた情報を紹介させていただきましたが、通常のテクスチャストリーミングのようにリクエストされていない部分も読むPrefetchは、VTのメリットが減ってしまい消費メモリも増加する可能性があります。

また改造も伴ってしまうため、UE5.4で行うおすすめの方針としましては、遅延が問題となるようなものはVTを利用しない、

もしくは既に試されているかもしれませんが、VTの設定で許容できる遅延まで解消できないか改めてお試しいただけますと幸いです。

参考URL

https://www.docswell.com/s/EpicGamesJapan/51NY7K-UE_CEDEC2022_CitySampleRenderingOptimize#p76

https://dev.epicgames.com/documentation/ja-jp/unreal-engine/runtime-virtual-texturing-in-unreal-engine

よろしくお願いいたします。

お世話になっております。

今回実装なされようとしている機能(VirtualTexturingの事前ロードを行う機能)につきまして、エンジンの機能を利用する箇所についてはアドバイスすることができますが、実装なされる機能の妥当性や効果につきましては御社側の実装内容による部分ですので、完全な機能の実装についての保証が出来かねる点につきましては改めて認識を共有できればと思います。また、ここまでのやり取りにおきまして実装部分の内容についてはコードベースでご共有頂く機会がございませんでしたため、コードベースで内容を共有できますと認識を一致させつつ具体的なやりとりができるかと思われます。現行の進め方でも問題ございませんが、マイルストーンの期限等もあるかと存じますので、必要に応じて代替案もご検討いただければと思います。

まずVTの基本的なフローとして以下のように流れていくことかと認識しております。VT機能の都合上、描画の結果反映までに1フレーム(以上の)遅延が発生する可能性があるという点がございます。

[GPU]
    │  UV, dUV → 必要Mip/タイル推定
    ▼
[TextureLoadVirtualPageTable()]
    │  → TextureLoadVirtualPageTableInternal()
    ▼
[Virtual Page Table Lookup]
    ├─ マップ済: 物理プールの該当タイルを参照
    └─ 未マップ: 最低ミップへフォールバック
                     │
                     ▼
        [StoreVirtualTextureFeedback()]
                     │  (必要タイルIDを記録)
                     ▼
     [CPUでFeedback集計 → Request生成]
                     │
                     ▼
   [Upload Tiles] + [Page Table Map]
                     │
                     ▼
         次フレーム以降は高解像に改善

事前ロードが効かないケースにおける1つのケースとして、[LoadObjectでUTexture → IAllocatedVT取得] -> [任意のタイルをRequest/Upload]

-> [実描画のUV, スタック, ミップと「ページID不一致」] ->[PageTable未マップ扱い → 最低ミップ表示 → Feedback→再ロード] ということが発生している可能性が考えられます。まずはリクエストしている PageIds が 一致しているかどうかは確認する価値があるものと思われます。ここでのPageIDsは、OnEndFrame_RenderThread で Feedback時に記録した PageIds と、RequestVirtualTextureTiles, およびLoadPendingVirtualTextureTiles で指定する PageIds の一致になります。

これが問題ないことが確認できた場合、リクエスト自体には問題がなさそうではあるが、VTの結果反映までのパスでネックになっていることが考えられます。r.VT.MaxUploadsPerFrame を上げてもポッピングに対して効果が無い理由はいくつか考えられますが、そもそも当該フレームの「アップロード対象」が少ない、Feedback→集計→反映の1フレーム遅延で頭打ち、他の制限要因との競合、などが想定されます。stat virtualtexturing で状況を確認しながら各種パラメータを調整することで改善されることがあります。一例として、以下のようなパラメータを確認するのは一つの調整の目安としてございます。

・Num page visible not resident が高い → 必要なページがロードされていない

・Num page update が低い → アップロード制限に引っかかっている

このような状況である場合、例えば以下のような調整によってポッピングが緩和される可能性がございます。

r.vt.FeedbackFactor 8

r.VT.MaxUploadsPerFrame 100

r.VT.MaxUploadsPerFrame.Streaming 100

あくまで上記は一例ですが、これはコンテンツ次第や実装なされている機能、および動作状況(他のVT利用状況)などによっても影響する部分ではございますので、そのままの値を利用せずに調整の目安としてご確認ください。特に、r.VT.MaxUploadsPerFrame の値を大きくすることは、アップロード数の増加に伴い、物理プールの蓄積に伴うGPUメモリ使用量の増加、およびCPU負荷の増大、I/Oの帯域逼迫などに関与するため、値自体を大きくしすぎないようにご注意ください。

それから、弊社澤田からも提案がありましたが、

>また改造も伴ってしまうため、UE5.4で行うおすすめの方針としましては、遅延が問題となるようなものはVTを利用しない、

>もしくは既に試されているかもしれませんが、VTの設定で許容できる遅延まで解消できないか改めてお試しいただけますと幸いです。

上記の点は代替案として妥当かと思われますので、バックアッププランの一つとしてご考慮頂けますと幸いです。

よろしくお願いします。

お世話になっております。

連続のご連絡となってしまいますが、追加の確認事項としましてVirtual Textureのプールが足りているかご確認頂けますでしょうか?

問題の状況にもよりますが、今回各種設定を行ってもポッピングが発生してしまうとのことですので、プールが十分に足りておらず低いミップが表示されてしまっている可能性も考えられます。

r.VT.Residency.Notify 1にて各種形式毎の消費%が確認でき、必要に応じてプロジェクト設定のEngine - Virtual Texture Poolから調整が可能となっておりますので一度ご確認頂けますと幸いです。

https://dev.epicgames.com/documentation/ja\-jp/unreal\-engine/virtual\-texture\-memory\-pools\-in\-unreal\-engine

よろしくお願いいたします。

お世話になっております。

各種確認及び情報を共有頂きありがとうございます。

​現状問題の解決に至れていないとのことで申し訳ございません。

来週直接お伺いして確認を行う予定となっておりますので、

頂いた内容を弊社内で共有し、こちらのスレッドは一度Pendingとさせて頂きます。

よろしくお願いいたします。​