Unreal Engine (UE) Advent Calendar 2025 シリーズ4 12日目の記事です。
概要
UE5.6から、デフォルトでVirtualTextureが有効になりました。
Enabling of virtual textures now defaults to on (but still defaults off for mobile).
(Unreal Engine 5.6 Release Notesより)
とはいえ、まだまだTextureStreamingは現役なので、TextureStreamingについての情報をまとめてみることにしました。末尾の参考資料にもある通り、先人のまとめの焼き直しな所も多々ありますが、改めてまとめることで自分の中の情報整理にもなるため、記事化しています。
TetxureStreaming周りを調査する時に注意すべき点
TetxureStreaming周りでよくある問題として、
- 瞬間移動やシーケンサーのカット切り替え時など、TextureStreamingが間に合わず、ボケたテクスチャが見えてしまう
- 目の前にあるメッシュのテクスチャが、なぜか常にボケたままになっている
- 画面左上にTEXTURE STRAMING POOL OVER xx.xxMiB BUDGETの赤文字が出ていて、メモリが足りてないっぽい
といった問題があります。まず最初に、これらの原因を調査する時に気を付けたほうが良いことを紹介します。
キャッシュの破棄
r.Streaming.DropMips 1 を必ず実行してください。
これを実行することで、現在の画面に必要な分以外はすぐに破棄するように変更されます。
そうすることで、あとどれぐらいメモリが余っているのかなど実際に必要なメモリ量が分かりますし、後述するキャッシュの挙動によって問題が隠蔽されてしまうことも防げます。
r.Streaming.DropMips 0がデフォルトですが、その際はStreamingPoolが不足していない限り、読み込んだテクスチャはメモリ上に(キャッシュとして)保持したままになります。
例えばとあるメッシュを映している状態から、カメラを横に振ってそのメッシュが画面外に出たとします。画面に映っていない(=使っていない)からと言ってすぐに破棄してしまうと、再度カメラに捉えた時に当然再読み込みする必要があります。つまり、カメラを横に振るだけで、読み込み/破棄が繰り返されることになります。
それでは効率が悪いため、StreamingPoolが不足していない限りは、キャッシュとして保持するようになっています。
逆にキャッシュとして保持してしまうため、初回読み込み時は問題が出ても、2回目に訪れた時は問題が起きない。でも、キャッシュが破棄された後にまた訪れると問題が起きる。といったことが普通に起きます。プレイヤーからはキャッシュが破棄されたかどうかを知る術はありませんので、再現性の低いバグとして報告されて開発者としては悩むことになりがちです…。
エディタではテクスチャを開かない(PIEで確認する場合)
エディタでテクスチャを開くと、そのテクスチャには FullyLoad の指定が入ります。つまり、ゲーム内のTextureStreamingの状況に関係なく、全てのMipmapを読み込むように指示されます。
その結果、正しいTextureStreamingの結果を確認できなくなりますので、PIEで確認する場合は各種エディットウィンドウを開かずに実行してください。
TetxureStreamingのPoolについて
次は、TetxureStreamingのPoolについて解説します。
TextureStreamingは、基本的に r.TextureStreaming.PoolSize で決められらた容量内でメモリのやりくりを行います。
足りなければ、画面の左上に

TEXTURE STRAMING POOL OVER xx.xxMiB BUDGETのエラーが出ますが、メモリ不足によるクラッシュはしません。その理由は、足りなくなった分を読み込んでいないから。
つまり、ワールド内のどこかでは必要な解像度が読み込まれず、ボケボケになったテクスチャがあります。(ぱっと見問題なさそうな場合は、単に見えてないか気づいていないだけ)
r.TextureStreaming.PoolSizeは実行時に変更できます。一時的に極端に低い値に変更してみると、ボケた世界が体験できるので、一度試してみると良いかもしれません。
PoolSizeの計算式
stat streamingで表示される各種値と、大元の r.Streaming.PoolSizeの関係性は以下の通りです
- StreamingPool = r.Streaming.PoolSize - (NonStreamingMips + TemporaryPool + SafetyPool)
- RequeiredPool = VisibleMips + HiddenMips + ForcedMips + UnknownRefMips
- StreamingPoolの余り = StreamingPool - RequeiredPool
ということで、そのシーンであとどれぐらい余裕があるかは最後の StreamingPoolの余り を計算すると求められます。
Windows版におけるStreamingPoolについて
前項のスクショを見てもらうと分かりますが
StreamingPool = r.Streaming.PoolSize - (NonStreamingMips + TemporaryPool + SafetyPool)
→ 1000.0MB ≠ 1000.0MB - (155.61MB + 50.0MB + 5.0MB)
と、なぜか計算式の通りになっていません。StreamingPoolの設定を見極める時に、結構混乱するポイントです。
これはPCなどの専用ビデオメモリ(=VRAM)を持つ環境に向けて、特殊な処理が入っているためです。
PCはグラフィックカードに搭載されているVRAMという別枠のメモリがあります。なので、VRAMが余っていればその分は使ってもシステムメモリを圧迫することは無く、ある意味自由に使用できます。
無駄に余らせていても勿体ないので、使用VRAMが一定量まではStreamingPoolのサイズを減らさないようにして、疑似的にStreamingPoolの割り当て量を増やす処理が入っています。
専用ビデオメモリを持たない、Unified Memory Architecturereを採用しているプラットフォーム(家庭用ゲーム機など)では、システムメモリとビデオメモリは共用です。つまり、指定された容量を守らないとシステムメモリが確保できなくなるため、前述のような処理は入っていません。
この一定量はWindowsEngine.iniなどに書かれている
[TextureStreaming]
; PoolSizeVRAMPercentage is how much percentage of GPU Dedicated VRAM should be used as a TexturePool cache for streaming textures (0 - unlimited streaming)
PoolSizeVRAMPercentage=70
で指定されます。この例(デフォルト)だと、グラフィックカードに搭載されているVRAMの使用率が70%まではStreamingPoolのサイズは減らさないようになります。
なお、家庭用ゲーム機と同じようにNonStreamingMips などの量を差し引きたい場合は
PoolSizeVRAMPercentage=0
と0を設定する事で同じ挙動になります。当然、エディタはWindows版として動作しています。そのため、もしエディタなどでもr.Streaming.PoolSizeの正確な値を調整をしたい場合は、この設定をいじると良いかもしれません。
NonStreamingMipsの扱いについて
前述の計算式内のNonStreamingMipsがあるせいで、例えばr.Streaming.PoolSizeを1000MBに設定していても、UIなどのNonStreamingMipsで800MB消費していたら、StreamingTextureには残りの200MBしか使えないことになります。その結果、TEXTURE STRAMING POOL OVER xx.xxMiB BUDGETのエラーによく遭遇するかもしれません。
r.Streaming.PoolSizeはTextureStreamingに関する設定ではありますが、肝心のStreamingTextureへの割り当ては優先度が低いです。そのため、足りないからといってr.Streaming.PoolSizeを増やそうとする前に、NonStreamingMipsに無駄なものが無いか精査してください。
NonStreamingMipsの扱いについて:その2
前項の挙動を見て、
NonStreamingMipsのおかげでStreamingPoolが圧迫されるのは困る!
というのは、その通りでそれを回避するための仕組みもありました。
具体的には、NonStreamingMipsをStreamingPool の計算から除外する。という新しい計算式が一部プラットフォームで導入されています。新方式を採用すると、r.Streaming.PoolSize≒StreamingPool となるため、StreamingTetxureの予算管理がしやすくなります。
ただ注意点としては、StreamingPoolの計算から除外されるだけで、実際に使用されるメモリ量が変動するわけではない点です。
例えば、r.Streaming.PoolSize=1000MBとした時に、NonStreamingMipsに400MBぐらいを見積もっている場合、新方式に変更したらr.Streaming.PoolSizeはNonStreamingMipsの分を引いた600MBに減らす必要があります。
そのままの設定だと、最大で1000MB+400MBの計1400MBを消費する可能性があり、環境によってはメモリ不足に陥る場合が出てきます。
新方式/レガシー方式の切り替え方
ここからはプラットフォームによって、少し対応が分かれます。設定変更が可能かどうかや、CVar(コンソール変数)の0/1の向きが違うので、注意してください。
-
Windows(D3D12RHI)
- デフォルトはレガシー方式です
-
D3D12.TexturePoolOnlyAccountStreamableTexture=1で新方式に切り替わります
-
Windows(D3D11RHI)
- 固定でレガシー方式が採用されていて、新方式を選択することはできません
-
Metal
- 固定でレガシー方式が採用されていて、新方式を選択することはできません
-
Vulkan
- 固定でレガシー方式が採用されていて、新方式を選択することはできません
-
OpenGL
- 固定でレガシー方式が採用されていて、新方式を選択することはできません
こうしてみると、D3D12とそれ以外。という感じにしか見えないですが、実は新方式で固定のプラットフォームが有ったり、コンシューマプラットフォームを含めるとちょっと見え方が変わってきます。
ソースコード上は UE::RHICore::UpdateGlobalTextureStats() 関数の呼び出しによって切り替えが行われています。この関数の呼び出しはRHIの実装によって様々なため、上記のようにプラットフォームによって挙動が分かれる結果になっています。
UpdateGlobalTextureStats()の第三引数(bool)次第なので、エンジン改造で挙動を変えたい場合は、UpdateGlobalTextureStatsで検索すると変更すべき箇所が見つかります。
新方式とレガシー方式のどちらを採用すべきか
新方式/レガシー方式ともにメリット/デメリットがあります。
レガシー方式である、StreamingTetxure、NonStreamingTextureを合わせたサイズで制限を掛ける場合、テクスチャ全体で使用するメモリを制御しやすくなります。
一方、TextureStreamingの枠組みにNonStreamingTextureが含まれるというのは分かりづらいです。NonStreamingTextureのせいでStreamingTextureが割を食うという構造も本末転倒感があります。それを考えると新方式の方が扱いやすそうです。
個人的には、NonStreamingTextureの使用量を監視しつつ、新方式を採用したほうが管理しやすいと思っていますが、メリット/デメリットを勘案して判断することになります。少なくとも、対応するプラットフォーム間で統一はしたいですね。
TetxureStreamingの優先順位
TEXTUREGROUPの設定には一部特殊な設定が割り振られているグループがあります。それは
- TEXTUREGROUP_Character
- TEXTUREGROUP_CharacterNormalMap
- TEXTUREGROUP_CharacterSpecular
のCharacter系グループです。これらは、TextureStreamingの中でも高優先度でストリーミング処理が行われるようになっています。TextureStreamingが間に合ってない時に、背景などよりキャラクターの方を優先して読み込むようになります。
あくまで、TEXTUREGROUP単位の設定のため、各テクスチャに適切にTEXTUREGROUPを設定しないと意味がありません。キャラクターのマテリアルで使用していてもTEXTUREGROUP_Worldのままでは、この恩恵は得られないので注意してください。
UE5.3ぐらいからは、HighPriorityLoadという設定も追加されています。TEXTUREGROUP単位ではありますが、以下のような設定を追加することで同じ高優先度状態を付与できます。
TextureLODGroups=(Group=TEXTUREGROUP_World, ... ,HighPriorityLoad=True, ...)
ここで設定する優先度はあくまで相対的なものです。あれもこれもHighPriorityLoadを設定してしまうと、意味が無くなってしまいますので、これも注意してください。
あとがき
まとめた後に見返しても、参考資料に挙げたQiita記事の焼き直し感がありますが、いくつか新規情報もありますし思い切って投稿しました。
TextureStreamingのPoolサイズの計算については、Windows版のせいでいつも混乱しているので、改めてまとめてみました。他にも必要なMipレベルの求め方も調べていたんですが、複雑すぎて上手くまとまらず今回は掲載を見送りました。
冒頭に書いた通り、VirtualTetxureがデフォルトで有効になりましたが、全てがVirtualTetxureで扱われる未来はまだもう少し先だと思うので、しばらくは参照される記事になっていると良いんですが。(少なくとも自分は見返しに来そうです
)
参考資料
この資料を書くにあたって参考にした資料です
