Unreal Engineのエディタ画面の解像度検証

# はじめに
　コンソールゲーム開発ではあまり意識しないのですが、PCゲームでは画面サイズの変更が普通に発生します。それでなくてもエディタで作業中は頻繁にサイズ変更が発生したりします。普段あまり気にしないことでしたが、実際に画面サイズ、つまりはViewサイズの変更で何が起きてるか確認してみました。
　きっかけはポストプロセスで画面解像度を取得したいって言われた事でした。

# 画面サイズを表示してみる
　ポストプロセスマテリアルで現在の画面サイズを表示してみます。画面サイズを表示する方法はいくつかありますが、今回は２種類の方法で取得してみます。こんなポストプロセスマテリアルを作成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147177/c71f217a-536e-71a5-d4ee-11d37d6ad595.png)
　左画面半分に表示したり、文字部分を見やすくするためにマスクしたりとちょっと小技が入っていますが、基本的には SceneTexture:PostProcessInput0のサイズとScreenResolutionのVisible Resolutionを表示しています。
　エディタを起動してマテリアルをセットした結果です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147177/3821c4b5-3580-f2a1-17a7-20ca419100c5.png)
　SceneTextureのSizeとVisible Resolutionが一致していないのがわかります。
　SceneTextureのSizeは1968x1246でVisible Resolutionが1355x858です。
　左右で値が違いますが、左はPostprocessのパスがAfter Tonemappingで、右はAfter Dofです。After Dofの場合は両者同じ値になっていますが、After Tonemappinの方はSceneTextureのサイズの方が大きくなっています。
　ここでちょっと解説すると、最近のUEはTSRによるアップスケールが前提となっていて、3D描画時の解像度と最終的な出力の解像度が一致しないのがあたりまえになっています。After Tonemappingはアップスケールが終わっている状態なのでScreenTextureのサイズがエディタViewの解像度、つまり見かけと同じになっていると思われます。
　UEエディタではこういう仕様っぽいです。スタンドアローンゲームで実行すると
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147177/afb043c5-248f-86a8-02b7-5225e6d09e42.png)
　一致しています。
　r.screenpercentage 50で描画中の解像度を50%にするとそれに応じた数値になります。
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147177/b06d0957-5fdf-579f-b0f0-228b2e939fb8.png)
　ここまではまあ、想定通りの挙動です。エディタの描画解像度と画面解像度が最初から一致しないのはちょっと気になりますが。なんとなくエディタでは描画と表示が同じ解像度になっていると思い込んでました。
　ここでエディタのViewの横サイズをドラッグで変更してみます。ここからが面白い挙動です。
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147177/b6a004db-25be-7dd6-fb0c-df3144785047.png)
　上の方は縦はそのままで横の解像度が減っています。当然の挙動です。が、下の方を見ると横の解像度は減っているのに縦の解像度が増えています。
 　縦を延ばすと今度は横の解像度が減ります。
　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147177/a59769cc-ab77-23ac-8909-37ec13823afd.png)
　縦横の比率が一定に変化すると思ってたので意外でした。 
　スタンドアローンゲームでも同様の挙動となり、r.setresコマンドで1280x720 -> 1600x900に変更すると
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147177/8648c793-e03c-f6a8-8c30-96b468842cb5.png)
　となります。ちなみにr.screenpercentage 100で描画サイズと画面サイズを一致させると
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147177/814644e9-c012-734e-2028-942ed366b21e.png)
　ちゃんと同じ数値になります。
　予想外の挙動なので何が起きてるか詳しく調べてみましょう。
## ScreenPercentage
　調査の前にここでScreenPercentageの話をしておきたいと思います。
　Unreal EngineでのScreenPercentageは主にゲームビルドで表示画面サイズに対して描画の画面サイズを小さくして描画負荷を下げるものです。描画する面積が減れば処理するピクセル数は減るので当然描画負荷は下がります。UE4からある機能で、当時はただ引き伸ばすだけだったのですが、UE5になってからはTSR(Temporal Super Resolution)という超解像技術が導入されたため、低い解像度で描画しても高解像度で描画したのと遜色のないクオリティで拡大できるようになりました。詳しくは公式ドキュメント[テンポラル スーパー解像度](https://dev.epicgames.com/documentation/ja-jp/unreal-engine/temporal-super-resolution-in-unreal-engine)などを見てもらうのが良いです。NVIDIAのDLSSやAMDのFSR、IntelのXeSSなども類似の技術です。最近のゲームは4Kが当たりまえになってきましたが、拡大無しにそのまま4Kで描画しようとすると最高クラスのGPUが必要になってきます。
　ゲームビルドではDynamic Resolutionという描画の負荷にあわせて動的にScreenPercentageを調整する機能とセットで使用されたりします。
　エディタでも描画負荷を下げる目的で調整されているんじゃないかと思いますが、エディタでr.screenpercentageを行っても機能しません。
 
## エディタViewportをドットバイドットで表示したい
　まず当面の目標として、エディタの解像度Viewportの解像度を実際のサイズに合わせたいと思います。
　エディタのScreenPercentage設定がどうなっているか検索したところ、この辺ぽいです。

 ```EditorProjectSettings.cpp

TAutoConsoleVariable<int32> CVarEditorViewportDefaultScreenPercentageMode(
	TEXT("r.Editor.Viewport.ScreenPercentageMode.RealTime"), int32(EScreenPercentageMode::BasedOnDisplayResolution),
	TEXT("Controls the default screen percentage mode for realtime editor viewports using desktop renderer."),
	ECVF_Default);

TAutoConsoleVariable<int32> CVarEditorViewportDefaultMobileScreenPercentageMode(
	TEXT("r.Editor.Viewport.ScreenPercentageMode.Mobile"), int32(EScreenPercentageMode::BasedOnDPIScale),
	TEXT("Controls the default screen percentage mode for realtime editor viewports using mobile renderer."),
	ECVF_Default);

TAutoConsoleVariable<int32> CVarEditorViewportDefaultVRScreenPercentageMode(
	TEXT("r.Editor.Viewport.ScreenPercentageMode.VR"), int32(EScreenPercentageMode::Manual),
	TEXT("Controls the default screen percentage mode for VR editor viewports."),
	ECVF_Default);

TAutoConsoleVariable<int32> CVarEditorViewportDefaultPathTracerScreenPercentageMode(
	TEXT("r.Editor.Viewport.ScreenPercentageMode.PathTracer"), int32(EScreenPercentageMode::Manual),
	TEXT("Controls the default screen percentage mode for path-traced viewports."),
	ECVF_Default);

TAutoConsoleVariable<int32> CVarEditorViewportNonRealtimeDefaultScreenPercentageMode(
	TEXT("r.Editor.Viewport.ScreenPercentageMode.NonRealTime"), int32(EScreenPercentageMode::BasedOnDPIScale),
	TEXT("Controls the default screen percentage mode for non-realtime editor viewports."),
	ECVF_Default);

TAutoConsoleVariable<float> CVarEditorViewportDefaultScreenPercentage(
	TEXT("r.Editor.Viewport.ScreenPercentage"), 100,
	TEXT("Controls the editor viewports' default screen percentage when using r.Editor.Viewport.ScreenPercentageMode=0."),
	ECVF_Default);

TAutoConsoleVariable<int32> CVarEditorViewportDefaultMinRenderingResolution(
	TEXT("r.Editor.Viewport.MinRenderingResolution"), 720,
	TEXT("Controls the minimum number of rendered pixel by default in editor viewports."),
	ECVF_Default);

TAutoConsoleVariable<int32> CVarEditorViewportDefaultMaxRenderingResolution(
	TEXT("r.Editor.Viewport.MaxRenderingResolution"), 2160,
	TEXT("Controls the absolute maximum number of rendered pixel in editor viewports."),
	ECVF_Default);
```
　結論から言うと
r.Editor.Viewport.ScreenPercentageMode.RealTime 0
　ですべて実解像度になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147177/1d90fd87-6f5b-8610-c9fe-a366959ef5c5.png)
　このCVarのパラメーターは

```LegacyScreenPercentageDriver.h
// Mode for the computation of the screen percentage.
UENUM()
enum class EScreenPercentageMode
{
	// Directly controls the screen percentage manually
	Manual UMETA(DisplayName="Manual"),

	// Automatic control the screen percentage based on the display resolution
	BasedOnDisplayResolution UMETA(DisplayName="Based on display resolution"),

	// Based on DPI scale
	BasedOnDPIScale UMETA(DisplayName="Based on operating system's DPI scale"),
};
```
　で、デフォルトではBasedOnDisplayResolutionになっているようです。BasedOnDPIScale、つまり2を設定すると、Windowsのディスプレイ設定のDPIScaleが反映されます。
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147177/56cee0b5-768d-ca08-86c1-894ee10bfdd2.png)
　この設定です。
 微妙に誤差はありますが、1.5倍になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147177/ba3b22f1-1644-b95f-4c03-63bc142fc32a.png)
　100%に設定されているディスプレイの場合は内部解像度と出力解像度が一致します。
 
 

# 画面サイズの変更の謎を追え
　ここまで調べた結果この現象の謎は
 - エディタでBasedOnDisplayResolutionにしたときの内部解像度がどう計算されているのか
 - Viewサイズを変更したときの内部解像度の再計算はどうなってるのか
 　の２点に集約されるのかなと思います。
　ということで、ソースコードを追いながら何が起きているか調べてみました。

## エディタでのScreenPercentage
　先ほど説明したとおり、描画と表示の画面サイズの違いはScreenPercentageでコントロールされていますが、どうもエディタとビルドで違うパラメーターなんじゃないかなと調べていきました。
 
 まず見つけたのがコレです。

```SceneRendering.cpp 
static TAutoConsoleVariable<int32> CVarTestPrimaryScreenPercentageMethodOverride(
	TEXT("r.Test.PrimaryScreenPercentageMethodOverride"),
	0,
	TEXT("Override the screen percentage method for all view family.\n")
	TEXT(" 0: view family's screen percentage interface choose; (default)\n")
	TEXT(" 1: old fashion upscaling pass at the very end right before before UI;\n")
	TEXT(" 2: TemporalAA upsample."));
```

```SceneRendering.cpp
		#if !UE_BUILD_SHIPPING
		// For testing purpose, override the screen percentage method.
		{
			switch (CVarTestPrimaryScreenPercentageMethodOverride.GetValueOnRenderThread())
			{
			case 1: View.PrimaryScreenPercentageMethod = EPrimaryScreenPercentageMethod::SpatialUpscale; break;
			case 2: View.PrimaryScreenPercentageMethod = EPrimaryScreenPercentageMethod::TemporalUpscale; break;
			case 3: View.PrimaryScreenPercentageMethod = EPrimaryScreenPercentageMethod::RawOutput; break;
			}
		}
		#endif
```

　CVarの説明では0,1,2なんですが、実際に使用しているところでは1,2,3で判定しています。
0 : マニュアル（デフォルト）
1 : 単純アップスケール
2 : TSR
3 : そのまま出力
　ってことみたいですね。
r.Test.PrimaryScreenPercentageMethodOverride 3
　にすると内部バッファサイズをそのままアップスケール無しに出力してくれるので、これはこれでデバッグや検証時に有用そう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147177/28a7362d-1a24-eba2-6dc4-a0d73f562a8d.png)

EScreenPercentageMode::BasedOnDisplayResolutionをもとにソースを検索するとどうやら

```LegacyScreenPercentageDriver.cpp
float FStaticResolutionFractionHeuristic::ResolveResolutionFraction() const
```
　ここで描画画面比率を計算しているみたいです。詳しくはソースを読んでもらえば良いですが、BasedOnDisplayResolutionの場合、縦の解像度をもとに16:9想定の画面ピクセル数、つまり面積を調整しています。調整のベースになる数値はBaseEngine.iniのこれらの項目が使用されています。

 ```BaseEngine.ini
[Rendering.AutoScreenPercentage]
MinDisplayResolution=720
MinRenderingResolution=720
MidDisplayResolution=2160
MidRenderingResolution=1080
MaxDisplayResolution=4320
MaxRenderingResolution=1440
```
　最低解像度、最高解像度、その間の基準解像度をもとに解像度を補完して求めているようです。
　縦ベースではありますが、面積で計算しているので、縦横どちらかしか変更しなくても縦横両方の数値が変化する結果になります。ここで求められた数値をもとに表示領域に対する描画領域のサイズを決めているみたいです。

## 考察
　なんでこんな処理になっているかを考察すると、近年のPCディスプレイの高解像度化により、4K,8KディスプレイでUEを使用する場合の解像度による描画の重さやVRAMの使用量を軽減する目的かと思います。確かに高解像度、高密度のディスプレイの場合は多少内部解像度が低くてもTSRなどでアップスケールされていてもそこまで気にならないのかなと思います。
　縦の解像度が倍になると面積は４倍になり、描画負荷も単純に４倍にならないまでも非常に高くなります。UE5で4Kディスプレイで作業しているとVRAMが8GB程度のGPUだと枯渇したりします。まあ無理もないかなと思います。
　ゲームビルドで使用するDynamicResolutionは描画時間を計測して既定値を超えそうになると描画解像度を下げるというものですが、こちらはエディタ・PCビルドを想定して設定解像度に応じて描画解像度を抑えるという仕組みですね。

## 対策
　「VRAMいっぱい積んでるし、エディタでは実寸で作業したいんや」というひとは、
r.Editor.Viewport.ScreenPercentageMode.RealTime 0
 を実行するかConsoleVariables.iniにでも追加しましょう。iniファイルに書くときは
r.Editor.Viewport.ScreenPercentageMode.RealTime=0
です。=を忘れずに
　または、「ある程度解像度が高くなるまでは補正されないで欲しい」という場合は、先ほどのiniファイルのResolution情報を大きめに調整すれば良いかと思います。

DefaultEngine.iniにこんなふうに追加すれば、ある程度View画面が大きくなるまでは拡大無しになります。数値はプロジェクトや環境にあわせて調整してもらえればと思います。
 ```DefaultEngine.ini
[Rendering.AutoScreenPercentage]
MinDisplayResolution=1024
MinRenderingResolution=1024
MidDisplayResolution=2160
MidRenderingResolution=1440
MaxDisplayResolution=4320
MaxRenderingResolution=2160
```
# 結論
　Unreal EngineエディタでもScreenPercentageで描画解像度を下げる仕組みがあることがわかりました。
r.Editor.Viewport.ScreenPercentageMode.RealTime
　でリアルタイム時
r.Editor.Viewport.ScreenPercentageMode.NonRealTime
　でリアルタイム無効時の設定が可能です。
　設定値は 0:(手動) 1:(解像度から設定) 2:(解像度とDPIスケールから設定)
　です。
r.Editor.Viewport.ScreenPercentageMode.NonRealTime 0
　でリアルタイム無効時だけ実解像度にするというのも良いかもしれません。
　RealTimeとNonRealTimeはエディタのここで切り替えられます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147177/250167b4-af21-ce98-5ec4-c5e9110fedb4.png)

　また、手動時は
 r.Editor.Viewport.ScreenPercentage
  で倍率が設定できます。ここで指定した場合は縦横の比率は保ったままで内部解像度が調整されるようです。
  その他いろいろ設定があるので試してみると良いでしょう。パストレース時のみの設定とか。
　自動設定の場合は
[Rendering.AutoScreenPercentage]
　の設定で制御することができるので、一定以上の解像度でのみScreenPercentageが有効になるといったことも可能です。
 
# まとめ
　Windowsのマルチディスプレイやら解像度やらDPIやら、とにかく面倒くさい。PCゲーム作るときはここが本当に面倒くさい。UE側で色々機能があるのでエンジンコードの中を調べると色々発見があります。知らなかったCVar設定とかザクザク出てきます。
　エディタのView画面解像度が気になったひとに役に立てば幸いです。ピクセルベースの2Dゲーム開発とかには有益なのかな？