【UE4】UMGのRetainerBoxがいつ更新されたかデバッグ表示するメモ

[neko_umg]:https://www.slideshare.net/EpicGamesJapan/umg-80334310
[umg_opt]:https://www.slideshare.net/EpicGamesJapan/ue4ui
[slatedeb]:https://docs.unrealengine.com/4.27/ja/ProgrammingAndScripting/Slate/ConsoleSlateDebugger/
[reflector]:https://docs.unrealengine.com/4.27/ja/ProgrammingAndScripting/Slate/WidgetReflector/
[insights]:https://docs.unrealengine.com/4.27/ja/TestingAndOptimization/PerformanceAndProfiling/UnrealInsights/SlateInsights/

:::note info
確認したバージョンは UE4.27.2 です。
:::

# 参考URL

- [猫でも分かるUMG - Slideshare][neko_umg]
- [UE4で作成するUIと最適化手法 - Slideshare][umg_opt]
- [コンソール スレート デバッガ - UE4公式ドキュメント][slatedeb]
- [ウィジット リフレクタ - UE4公式ドキュメント][reflector]
- [Slate Insights の概要 - UE4公式ドキュメント][insights]

# Retainer Box とは？

- 更新頻度が低くても構わないウィジットに有効。
- 子ウィジットをテクスチャレンダリングして、そのテクスチャを表示する。
    - 更新頻度が低くてよいときなどに使用する。
    - なので、毎フレームレンダリングさせてしまうと逆に重くなる。

**参考URL**

- [猫でも分かるUMG - Slideshare][neko_umg] の115ページ目
- [UE4で作成するUIと最適化手法][umg_opt] の127ページ目

## Render on Phase

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/58473f7f-9ffa-63ac-da5f-f83fe7193d75.png)

一定周期でレンダリングする。
(上のスクショならば、30フレームに1回)

![20211220_01_RetainerPhase.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/f5d7d0c2-650a-4616-2bed-d028565e7899.gif)

Phaseを変えると、その30フレームに1回のうちの何フレーム目にレンダリングするかを指定できる。

- 【一番左】Retainer Box を使わない普通の状態。
- 【左から２番目】Retainer Boxで毎フレームレンダリングしたもの (重い)
- 【左から３番目】Retainer Boxで30フレームに一回レンダリングしたもの
    - 30フレーム周期の0番目のタイミングでレンダリング
- 【左から４番目】Retainer Boxで30フレームに一回レンダリングしたもの
    - 30フレーム周期の15番目のタイミングでレンダリング

## Render on Invalidation

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/6224e8d5-1e26-96e4-49ec-e7f20ec32936.png)

- 内容に変更があればレンダリングする。(サイズやレイアウトの変更など)
- RequestRender ノードを使えば、任意タイミングでレンダリングを行える。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/dfe2184a-b6b7-864b-70eb-14e77de17712.png)

ボタンを押すたびレンダリングする例：

![20211220_02_RetainerInvalidation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/11b3c090-04ab-38ee-fca5-745c773bfee0.gif)

# 本題。いつ更新されたかをデバッグ表示したい

[コンソール スレート デバッガ][slatedeb]を使う。
…のだが、公式のリファレンスを見てもなんだか良く分からない。
以下は、結局ソースを追いかけて分かったことのメモ。

Retainer Boxの更新タイミングをデバッグ表示するコンソールコマンドは：
`SlateDebugger.InvalidationRoot.Start`
(これを終了するには`SlateDebugger.InvaidationRoot.Stop`)

|デバッグ表示色|意味|
|:---:|:---|
|<font color="red">赤</font>|Slow Path (レンダーテクスチャのリサイズ(再確保)が必要な場合)|
|<font color="green">緑</font>|Fast Path (レンダーテクスチャのサイズ変更が不要な場合)|
|<font color="blue">青</font>|None (何も更新していない)|

:::note warn
2021/12/23 Slow Path, Fast Path の説明文を修正しました。

<font color="gray">誤　Slow Path (レンダリングを行っている)</font>
<b>正　Slow Path (レンダーテクスチャのリサイズ(再確保)が必要な場合)</b>

<font color="gray">誤　Fast Path (どうやらInvalidation Boxで使っているようだ…たぶん)</font>
<b>正　Fast Path (レンダーテクスチャのサイズ変更が不要な場合)</b>
:::

以下のような絵になる:

![20211220_03_RetainerPhase_DebBad.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/7dd79c0e-c4cf-0300-2ba5-394be1a79d89.gif)

問題は、すべてのRetainer Boxが、終始「<font color="red">赤</font>」で表示されている点である。
「こんなハズではない！」と変な汗をかいてしまう。
ソースを追いかけて知ったのだが `FConsoleSlateDebuggerInvalidationRoot`クラスには`CacheDuration` なる秒数を保持する変数があり、Slow Path/Fast Path/None が変わった後「CacheDuration」秒だけかけて色をゆっくりと変えているようだ(lerp)。
で、このデフォルト値が2.0秒。
つまり、30フレームに1回程度の更新では、lerpが追いつかず、ひたすら赤のままになるわけである。
何という罠。

## というわけで、CacheDuration を任意に変更したい

私のWeb検索力では、このことについて言及した記事を見つけることができなかった。
ソースを調べると、以下の.iniファイルを書き換えることで可能だと分かった：
`<プロジェクトDir>/Saved/Config/Windows/EditorPerProjectUserSettings.ini`

このファイルにはかなりたくさんの項目があるが、その中の`[SlateDebugger.InvalidationRoot]`を探す。(ないなら手動で追加する)

```ini
[SlateDebugger.InvalidationRoot]
bDisplayInvalidationRootList=True
bUseWidgetPathAsName=False
bShowQuad=True
DrawSlowPathColor=(R=255,G=0,B=0,A=255)
DrawFastPathColor=(R=0,G=255,B=0,A=255)
DrawNoneColor=(R=0,G=0,B=255,A=255)
MaxNumberOfWidgetInList=20
CacheDuration=2.000000
```

最後の`CacheDuration`を任意の秒数 (例: 0.15 秒) に変更するとこうなる：

### Render on Phase版

![20211220_04_RetainerPhase_Deb.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/71f0408f-4f11-0e3f-70f8-04d0c861e842.gif)

Phase Count = 30の２つは Phase = 0 と 15 の異なるタイミングで更新されているのが分かる。

### Render on Invalidation版

![20211220_05_RetainerInvalidation_Deb.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/5dfbb8f3-487d-015d-e4ff-faca7429c65e.gif)

ボタンを押して RequestRender を呼び出したときだけ更新されているのが分かる。

## 関連するエンジンソース

- `class URetainerBox`: Retainer Boxウィジット。
- `class SRetainerWidget`: Retainer Boxウィジットが持っている Slate。これが本体。
- `class FSlateInvalidationRoot`: Retainer Box, Invalidation Boxの基底クラス。
    - これが更新の可否(Fast Path, Slow Path, None)を決めている。
    - Fast Path, Slow Path, None は、このクラス宣言の少し上に`enum class ESlateInvalidationPaintType`として宣言されている。
- `class FConsoleSlateDebuggerInvalidationRoot`: [コンソール スレート デバッガ][slatedeb]の InvalidationRoot の機能。

# 雑記

- [Widget Reflector][reflector] はコンソールスレートデバッガの機能を呼び出しているようだ。
- Slate Insights は[公式ドキュメント][insights]の通りにやれば使える。
    - だが、ズーム機能がうまく働かなくて、個々のフレームの状態を選択するには、非常に微妙なマウス操作を要する。改善してほしいなぁ。