Trying to have smooth credits

Hello!

I’m trying to optimize my in game credits to get a smooth displacement, because it looks laggy.

I have a Widget which contains some stuff for the background and an Overlay in which I inject Widgets on the fly because our credits are data driven.

I’m using a WidgetPool, and made sure to never remove from parent, just change the position of an injected widget when it’s off screen and reset its texts.

(Type of widgets: First animated widget, Horizontal list for image(s), Simple text, Team List (3 rich texts: title, names, roles)…)

All those widgets contains an Invalidation box with the content under it.

I’ve disabled the Tick Frequency because I don’t need any animation, and disabled as well their Pixel Snapping (Note that with or without, it doesn’t change anything).

And I’m moving all my widgets by changing the Y position of their slots in a tick callback.

I’ve also tried to change the RenderTransform position instead but I have the same problem.

I took a video to check frame by frame if the widgets are moving on each frame but it’s not the case, it’s more like this:

Move, don’t move, move, move, don’t move, move, don’t move, don’t move, move… (A bit randomly)

So I’ve tried to not calculate any position and just force to change their position by one pixel on each frame (using FMath::Floor to ensure that the position is rounded) just to make sure it’s not a kind of pixel perfect issue, and I have the same result.

I’ve also took a trace with Insight and Slate insight activated, and I don’t see anything wrong, no weird spikes or anything.

My root container is invalidated all the time because I’m moving its childs, those childs are only invalidated when I change their content, and I never see them in the “Update” section because they all have an invalidation box.

I tried to use a FCurveSequence as well to receive the tick from elsewhere, tried to do some maths with a kind of fixed framerate, but nothing is working.

It doesn’t seems related to texts, because even a widget just containing an image is not refreshed properly on each tick.

I’ve then tried to force widget invalidation by calling InvalidateLayoutAndVolatility after moving the position on each frame, but still have this issue.

I saw since a while that pretty much all the games using UE are having this lagging issue in the credits, even in Fortnite I saw yesterday that it’s not perfect, so I’m wondering if there is any better solution or if we need to live with that problem?

Thanks!

[Attachment Removed]

Steps to Reproduce
Inject widgets containing texts in an overlay, and move their Y position in a tick method.

[Attachment Removed]

Hi,

My first thought here was pixel snapping since it’s responsible for the majority of stuttering credits screens that I see; you mentioned disabling it, was it disabled on everything in the hierarchy that would be moving? It *should* only need to be disabled on the text itself (or whichever widget is being painted), but I’d definitely expect a visible difference with that disabled, and your video should show some frames where the text looks blurry since it’s between pixels. Your description of the widget moving some frames and not moving other frames does sound a lot like it’s being rounded, so I’d give that second look to verify.

I’m not sure invalidation will make much of a difference here unless you’re noticing a framerate drop (which would be a bit surprising for a credits screen), it does sound to be more of an animation evaluation issue.

The other thing you might try would be to drive the movements with Sequencer (e.g. the UMG animation panel) instead of on tick, as it has a much more precise evaluation frequency. I’ve also seen folks put everything into a massive scrollbox and just animate the scroll position, that’s quite expensive from a memory standpoint and you might see a hitch when all of the credit widgets are initially created but the paint cost should be reasonable since most things will ultimately be culled.

If none of these suggestions solve things, are you able to share the video or a sample project? If you’re multiplying by DeltaTime in your tick logic and pixel snapping is disabled, I’d definitely expect a smooth animation here.

Best,

Cody

[Attachment Removed]

Hi Cody!

Thanks for your answer and sorry for the delay, so I made more tests based on your suggestions and more research, I’ve finally ended up to increment a frame counter in my tick and update a text on screen with it, and it appears that all my code is valid, and optimized, the problem is the rendering itself.

From a rendering prog on my team with who I’ve check the issue yesterday:

“Basically, some game thread frames are calculated, but some of them are dropped / not rendered, because the render thread drop them or because the GPU is too slow/fast, or because it’s not send to the GPU, something like that.”

What we can see with a video capture is this for the frame counter text:

…, 40, 41, 42, 42, 43, 44, 45, 45, 46, 47, 48, 48, 48, 50, …

When frames are not rendered I can also see that the 3D world is frozen on those frames as well.

We will see later if we can find a way to make sure all frames are rendered when we are in the credits, except if you already have an idea for us of course, or know someone who could!

But all of that to say that the issue is not the UI itself

Thanks again a lot for your time, and if we find a solution (if we have time to investigate more…), I’ll update this thread!

[Attachment Removed]

Glad to hear you tracked down a cause! If you multiply whatever movements you’re doing in your tick function by the DeltaTime, I’d expect the animation to still be fairly smooth. That said, if you’re dropping frames then you may have vsync enabled (and be missing your target refresh rate), or there are a few other CVars you could try tweaking during the credits:

  • r.GTSyncType - Defaults to 0, but using 1 or 2 shortens the slack and can be more likely to cause frame drops if the GPU is taxed
  • rhi.SyncInterval - If you’re using 1 (60hz) or 2 (30hz) you may be missing your target, missing on 30hz would be much more noticable
  • r.FinishCurrentFrame - Should be 0 unless you’re aiming for very low latency, in which case you may see frame drops
    [Attachment Removed]