サイズの異なるUIテクスチャの切り替えによるVRAM使用量削減について

この質問は、以下のスレッドに関連して作成されました: [Content removed]

<br/>

関連のスレッドで相談させていただいておりましたが、長期間返答ができておらず大変申し訳ございません。

前回ご提案いただいた解決方法aについて検証を行いましたので、改めて本スレッドにて相談させていただきたいです。

<br/>

解決方法aについて検証したところ、新たに以下2つの問題が発生いたしました。

<br/>

●検証方法

<br/>

FStreamableManager::RequestAsyncLoad()で切り替え後のテクスチャをロードする。

FStreamableManager::RequestAsyncLoad()のCallbackが呼ばれた次のUUserWidgetのNativeTick()でUMaterialInstanceDynamic::SetTextureParameterValue()を使用してマテリアルのテクスチャを切り替える。

差し替えと同時に、FStreamableManager::Unload()を使用して切り替え前のテクスチャをアンロード

●発生した問題

<br/>

問題点①:テクスチャの切り替え時に、一瞬(1秒未満)、添付画像のような表示となる。

Mip2相当のテクスチャからMip0相当のテクスチャに切り替える際に

<br/>

Mip2相当のテクスチャ → 添付画像 → Mip0相当のテクスチャ

という挙動になります。

なお、確認した限りではMip0相当からMip2相当への切り替え時はこの問題は発生していません。

<br/>

問題点②:アンロードしたテクスチャ分のVRAM消費量が即座には解放されない。

ガベージコレクションのタイミングで解放されているようです。

今回の用途では地図の拡大・縮小や移動で頻繁に切り替えが発生するため、不要なテクスチャでVRAMが圧迫されることを懸念しています。

なお、試しにUEngine::ForceGarbageCollection()を使用したところ、即座に解放されますが一時的に負荷が高まりカクツキが発生しました。

<br/>

●質問

<br/>

問題①について、この現象が発生する原因は何でしょうか。また、どのように対応を行えば解決できるかご教示いただけますでしょうか。

問題②について、VRAMの解放は定期的に自動実行されるガベージコレクションに任せておけば問題ないのでしょうか。問題がある場合、どのように解放を行うのが適切でしょうか。

<br/>

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

<br/>

[Attachment Removed]

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

ご質問ありがとうございます。

■問題①

前回のスレッドでご提案した解決策 a, b のうち a を採用されたとのことですが、 a については、

・a. **【非ストリーム」】**低解像度版と高解像度版のテクスチャアセットを別物にして、Mipレベルではなく2解像度レベルの切り替えを独自に実装する

という形で、低解像度 Mip なし1枚と、高解像度 Mip なし 1枚を用意し、ストリーミングなしで独自に切り替えを行っていただく想定でございました。

ご連絡いただいた症状をお聞きする限りでは、

・【お手元の設定】切り替え先のテクスチャがMipありのストリーム設定となっている

・【UEの挙動】RequestAsyncLoad の完了は「アセットのロード完了」であり、「全MipのVRAM常駐完了」ではない

という2つの事情から、先にVRAMに送り込まれた低解像度Mipから表示が始まっているのではないかと推測しております。

非ストリーミングテクスチャ(Mipなしテクスチャ)であれば、RequestAsyncLoad の完了時点で、そのテクスチャにおける最高解像度のみが利用可能となるはずです。念のため、切り替えにお使いの低解像度版・高解像度版それぞれのテクスチャアセットについて、以下の設定をお知らせいただけますでしょうか。

- Mip Gen Settings(NoMipmaps かどうか)

- Texture Group(UI かどうか)

- Never Stream のオン/オフ

- テクスチャの縦横サイズ(2 のべき乗かどうか)

また、もし高解像度版はMipありでいきたい、という場合は、RequestAsyncLoad だけで待つのではなく、そのあとSetForceMipLevelsToBeResidentでMipレベルを強制指定し、 IsFullyStreamedIn()をポーリングして準備完了を待つという方法もご案内できるかもしれません。一度、設定と方針のご確認をお願いいたします。

■問題②

問題②に関しましては、上述のお話次第でまた議論するべきトピックが変わってくるとは思いますが、一旦独立したご質問として回答申し上げますと、

まず、FStreamableManager::Unload()でアンロードの指示を出した場合、内部的にはハンドルの参照が切られるだけで、VRAM破棄が直接トリガーされるわけではありません。

実際には、GC周期で未参照状態になったハンドルが回収され、そのハンドルが管理しているテクスチャリソースが解放され、それが実体であるRHIのリソースを解放し、結果としてVRAMが削減される、という順序となります。

GCに頼らずVRAMを解放するには、UMaterialInstanceDynamic::SetTextureParameterValue()でテクスチャの差し替えが済み次第、旧テクスチャの ReleaseResource() を呼び出していただくのが有効です。疑似コードで表すと以下のような手順となります。

MID->SetTextureParameterValue(ParamName, NewTexture);   // 差し替え(従来どおり)
 
OldTexture->ReleaseResource();        //  即時解放(数フレームで VRAM 返却)
StreamableManager.Unload(OldPath);    // ハンドル解放(従来通り)
 
// UnloadされたストリーマブルハンドルはGC周期がくるまで解放されない
// しかしハンドルが握っていたVRAMは先行して返却される
// よってハンドルの回収は、ForceGCでスパイクを作ることなく、GCの周期にゆだねられる

一度ご確認いただけますと幸いです。

以上、よろしくお願いいたします。

[Attachment Removed]

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

■問題①

低解像度版と高解像度版のテクスチャはいずれも「Mipなし」にする方針です。

テクスチャ設定は以下の通りで、「Mipありのストリーム設定」にはしておりません。

  • Mip Gen Settings(NoMipmaps かどうか)
    • NoMipmaps
  • Texture Group(UI かどうか)
    • UI
  • Never Stream のオン/オフ
    • オン
  • テクスチャの縦横サイズ(2 のべき乗かどうか)
    • 低解像度のテクスチャ:1544×728、高解像度のテクスチャ:6176×2912

また、追加の補足になりますが、この問題は毎回発生するわけではなく数回に一度の頻度で発生します。

起動後の初回表示時であっても問題が発生しないケースが多々ありますので、リソースがVRAMに存在しない場合に必ず発生するというわけではございません。

以上を踏まえ、原因や解決方法について何かお分かりになることがございましたらご教示いただけますと幸いです。

■問題②

ご提案いただいたように、旧テクスチャの ReleaseResource() を呼び出すことで、スパイクを発生させることなく1秒ほどでVRAMが解放されました。

こちらの方法を採用したいと思います。

ありがとうございました。

[Attachment Removed]

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

​

​その後調査を進めたところ、問題①が発生する際、該当のテクスチャの FTexture2DResource::IsProxy() が true を返すことが分かりました。

そのため何かしらの原因により、一時的に仮のテクスチャリソースが使用されているものと考えております。

これを踏まえ、以下2点について確認させてください。

  • IsProxy() をポーリングし、false になるまで待機するという方法は、適切な解決方法となるのでしょうか?
  • 現状問題①はエディタ起動の場合のみ確認しておりパッケージ版では問題の発生を確認しておりませんが、パッケージ版でも同様の問題が発生する可能性はあるのでしょうか?

​お手数ですがご確認をお願いいたします。

[Attachment Removed]

ご返答いただきありがとうございます。

ご回答いただいた内容をもとに、解像度レベルの切り替えを独自に実装することができました。

長らくの間サポートいただき、誠にありがとうございました。

[Attachment Removed]

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

​

調査および共有をありがとうございます。確かに、エディタで非クックデータのままゲームを動かす場合、テクスチャはロード時にオンデマンドでプラットフォームデータへコンパイルされます。このコンパイルの間は仮テクスチャ(プロキシ)で表示を凌ぐ仕組みがあり、その結果として仮テクスチャが画面に出ているものと思われます。

​

> IsProxy() をポーリングし、false になるまで待機するという方法は、適切な解決方法となるのでしょうか?

​

はい、IsProxy() をポーリングしていただくのは適切な対応です。エンジンにも類似の実装を行っている箇所があります。

​

> 現状問題①はエディタ起動の場合のみ確認しておりパッケージ版では問題の発生を確認しておりませんが、パッケージ版でも同様の問題が発生する可能性はあるのでしょうか?

​

今回の仮テクスチャの問題は、パッケージ(クックしたデータ)では発生いたしません。

​

なお、今回の高/低解像度テクスチャの動的な差し替えにあたっては、 ReleaseResource() 済みの高解像度テクスチャを GC 前に再利用するようなケースもあると思います。このケースでは、 GetResource() が null を返しますので、 UpdateResource() で再生成する処理を入れておきますと、より動作が確実になるかと存じます。

FTextureResource* Resource = NewTexture->GetResource();
if (!Resource)
{
    NewTexture->UpdateResource();   // ReleaseResource() 済みの再利用時の復旧
    return;                         // 次の Tick で再判定
}
 
// すでにご導入済みのIsProxy()チェック...
if (Resource->IsProxy())
{
    return;
}
 
// テクスチャの差し替え...
MID->SetTextureParameterValue(ParamName, NewTexture);

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

[Attachment Removed]

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

ご連絡ありがとうございます!お役に立てたのであれば大変光栄です。​

それでは本件は解決済みとしてCloseさせていただきます。​

今後も何かお困りのことがあれば、お気軽にEPSをご利用ください。

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

[Attachment Removed]