特定条件下においてRetainerBoxの描画が崩れる

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

特定条件下においてRetainerBoxの描画が崩れる問題に対しての解消方法をご相談させてください。

Viewport領域にPlayerScreen領域をマスクしたカットアウト画像を描画

といった処理の実装を行っています。

描画負荷が高いため「Viewportサイズに変更があった場合のみ再描画」としているのですが

表示崩れにより期待した結果になりません。

UGameViewportClient* gameViewportClient = GetGameViewportClient();
if (!gameViewportClient || !gameViewportClient->Viewport)
{
	MyUserWidget = nullptr;
	return;
}
//
gameViewportClient->Viewport->ViewportResizedEvent.AddWeakLambda(this, [this](class FViewport* InViewPort, uint32 InVal)
	{
		//	ビューポートサイズ変更時に再描画要求
		if (MyUserWidget)
		{
			MyUserWidget->OnViewportSizeChanged();
		}
	});

再描画処理をViewportのサイズ変更時ではなくTickで常時行うと期待した結果になるため

実行タイミングの問題に見受けられます。

(「/Game/Widget/WBP_UserWidget.uaaset」のイベントグラフを編集すると確認可能です)

こちら期待した挙動を得るためのワークアラウンドなどありますでしょうか。

備考:

UE5.6、UE5.7、UE5.8で再現したのですがバージョンにより崩れ方が若干異なりました。

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

[Attachment Removed]

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

本事象は ViewportResizedEvent の発火タイミングだけに再描画を依存していることが要因です。ViewportResizedEvent はビューポートのリサイズ検知には有効ですが、RetainerBox 側の描画に必要な状態(UIジオメトリ確定やマスク計算条件)が同フレーム内で揃わない場合があり、表示崩れが発生します。また、マスク対象が PlayerScreen ベースの場合、再描画が必要になる条件はリサイズ以外(カメラ, FOV,ViewportScale 等)にも存在するため、リサイズ時のみの更新では不足するケースがあります。Tick で毎フレーム再描画すると崩れないのはこの不足分を毎フレーム吸収できるためです。

対応方針として、ViewportResizedEvent で直接 RequestRender() は行わずにここでは再描画予約フラグのみ設定し、その後次フレームの Tick(または SetTimerForNextTick)でフラグを消費して OnViewportSizeChanged() → RequestRender() を1回実行するようにして頂くことが推奨される対処方法となります。

[Attachment Removed]

ご返信、ご検証結果を共有頂きありがとうございます。

​前回お伝えした「次フレームの Tick(または SetTimerForNextTick)で再描画」という表現はやや曖昧で混乱を招いてしまい失礼いたしました。

ご指摘のとおり、単なる「次 Tick」では不十分で、次エンジンフレームまで待ってから再描画する必要があります。SetTimerForNextTick も、設定によっては同一エンジンフレーム内で実行されることがあります。改めて、ViewportResizedEvent では RequestRender() を直接呼ばずフラグのみ立て、次エンジンフレームで OnViewportSizeChanged() → RequestRender() を1回実行する形がよろしいかと思われます。

PlayerScreen マスクについて、リサイズ以外(カメラ・FOV・ViewportScale 等)でも再描画が必要という点について、残念ながら一括で通知する専用のデリゲートというものは用意されておりません。Viewport サイズは ViewportResizedEvent(再描画は次エンジンフレームへ遅延)、DPI やディスプレイ変更は FSlateApplication の OnWindowDPIScaleChanged や OnDisplayMetricsChanged、ViewTarget 切替は FGameDelegates::ViewTargetChangedDelegate などが利用できるかと思われます。カメラの回転や FOV、PlayerScreen 上の矩形の連続変化には専用デリゲートがないため、毎フレームまたは Tick で状態を比較し、変化があったときだけ RequestRender() する判定が現実的な方法になってくるかと思われます。

[Attachment Removed]

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

ご教示いただいた「ViewportResizedEvent の 次Tickで再描画を行う」を試行したのですが

期待した結果を得られなかったため「次Tickではなくさらに次Tick」で再描画を行うと期待した結果になりました。

類似した処理を比較した結果

■Delay Until Next Frame → OK

■Set Timer for Next Tick by Event

・TimerManager.GuaranteeEngineTickDelay 0 → NG

・TimerManager.GuaranteeEngineTickDelay 1 → OK

■Delay Until Next Tick

・LatentActions.GuaranteeNextTickDelay 0 → NG

・LatentActions.GuaranteeNextTickDelay 1 → OK

となったので次エンジンフレームでの再描画が必要に見受けられますが

理解としてあっておりますでしょうか。

また

  • PlayerScreen ベースの場合、再描画が必要になる条件はリサイズ以外(カメラ, FOV,ViewportScale 等)にも存在するため、リサイズ時のみの更新では不足するケースがあります。

とご指摘いただいた部分についてですが

こちらの条件を検知できるデリゲートなどはありますでしょうか。

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

[Attachment Removed]

ご回答ありがとうございます、諸々承知いたしました。

いただいた情報を元に実装方法を検討したいと思います。

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

[Attachment Removed]