Please help a non-graphics engineer understand the VSync spikes I’m seeing in the Profiler. Some details about my project:
- 10,000 spheres, moving in random directions (via Bursted jobs)
- Hybrid Renderer V2
- HDRP
- 4k resolution
- Profiling a development build (not PlayMode in the Editor)
With VSync visualization turned off, the Profiler shows a steady-ish 16ms/frame on the CPU:
With VSync viewing turned on, I get regular spikes up to 33ms/frame:
Here’s a view of what I’m profiling:
In Timeline, I can see that WaitForTargetFPS fluctuates wildly between 0.01 ms, and 19ms every few frames.
In the Rendering Profile section, I can see that my draw calls remain rock-steady between 74 and 75 per frame. No value in that panel is showing frame-over-frame variation.
I understand the basics of how VSync works, but what I don’t understand is what would cause it to spike every other frame. I would assume that if my CPU and GPU loads were relatively stable frame-over-frame, that the VSync wait period would be as well.
Please help me understand what’s going on here. From other forum threads, I believe I’m just under-educated in this area.
- Is this just an unavoidable limitation of rendering this many moving spheres on screen?
- Is this a current limitation of the Hybrid Renderer, which might improve in the future?
- Is this something I’m causing in my code, which I could optimize?
- Something else?
You can see in the first Profiler shot, that my jobs have a 10-15% speed variance on different frames, but it doesn’t seem to match up with the VSync spikes in the second shot. So I currently doubt that the spikes are caused by frames with slower jobs. But I can’t rule it out without more knowledge about what’s actually happening.
Thank you sincerely for any insights.
…Could these spikes be caused by the profiler itself?
How can be sure, one way or another?
Seems to perform much better with VSync disabled (in Project Settings):
But my novice understanding of VSync makes me believe this is not really a viable approach, since it risks screen tearing.
But maybe this ‘No Vsync’ shot can offer useful data to someone with more understanding.
Still trying to learn more…
Here’s the same scenario, but without movement. I’m just placing the spheres at random positions once, and then doing nothing:
This is more like what I would expect to see. Relatively stable WaitForTargetFPS values, frame-over-frame (though I don’t know what the dips are).
This would suggest that it is my code that’s causing the spikes, but I’m really not sure how.
In my OP example, my CPU and GPU work loads seemed to be about the same every frame. Wouldn’t this mean that my WaitForTargetFPS values should be about the same each frame as well?
Unlikely. Also that thread is 3 years old and concerns a 5.x version of Unity. “Unaccounted *” samples like this should always be reported as bugs so that we can close the gaps in Profiler marker coverage of native code and we’ve closed quite a few since back then. I haven’t seen a bug for these since quite a while now.
Now, regarding vSync. With this enabled it is ensured that a frame that the GPU finished processing is only flipped to be displayed during a vBlank, which occures every 1sec/ Screen refresh rate. So with a 60 Hz Monitor, that’d be every 16.66ms. If the GPU is done even slightly later than that, it’ll have to wait for the next one. Since the CPU main thread frame hands off to the Render thread before continuing to the next frame, this gfx.WaitForPresentOnGfxThread stall will appear on the frame after the one that issued the commands to the GPU, with the Render thread frame from the last main thread frame overlapping and still waiting for the GPU to finish presenting.
Btw, there’s an upcoming Unite Now talk that covers this topic further.
So to answering why you see your frames fluctuating every other frame:
Look at Timelineview and check neighboring frames. You can click and drag on empty space in the view to select a timeframe. With this you can measure the time from one frame being presented (Gfx.PresentFrame on the Render thread finishing) forward to where the next vBlank should have happened (depending on the screen refresh rate, e.g. 16.66 Ms for 60 Hz).
It could be that one frame it just fits but then causes run-in issues in the next frame, due to how close it was and the just miss it.
Similarly in your later screenshots it seems more stable at taking just a bit too long but ever now and the still making the cut for the first vBlank at 60 fps/Hz
So, check timeline view and see if you can make out the difference between these frames. Remember that the Job threads usage and the main thread depending on work from jobs to finish means it’s not just the main and render threads you need to look out for.
Thank you for this response! I’m making some builds and digging into the profiler now, as you suggested.
One question, in the meantime:
Given what you explained, wouldn’t that mean Gfx.WaitForPresent should never last more than 16.66ms (the time until the next VBlank)? I was seeing 19ms wait times in the Timeline view, on frames with those spikes.
No and not just because QualitySettings.vSyncCount could be >1.
This sample occurres on the main thread, while the render thread might not be done rendering and might not have handed off to the GPU yet. And even the Gfx.PresentFrame on the render thread could take longer because the render thread could start waiting before the GPU is done processing the frame.
The CPU Profiler documentation section on common samples covers these samples too, even if it currently isn’t going maybe as much into the details. I’ll consider expanding this further there.