I added a short clip showing how it looks like my emulator is skipping frames. In the beginning of the clip, it plays out fine, but I get to a point where the time just goes crazy fast. In some other areas, it's normal again.
I'm not sure what the cause could be, but I'd appreciate any input.
SMB skipping frames
Moderator: Moderators
-
- Posts: 22
- Joined: Sat Feb 01, 2025 3:30 pm
SMB skipping frames
- Attachments
-
- SMB.mp4
- (2.25 MiB) Downloaded 45 times
- TakuikaNinja
- Posts: 219
- Joined: Mon Jan 09, 2023 6:42 pm
- Location: New Zealand
- Contact:
Re: SMB skipping frames
Maybe something in your program flow is causing the frame pacing (i.e. waiting before/after presenting the framebuffer to the graphics library/framework) to be skipped?
Re: SMB skipping frames
This is more about how to do the advancing of time in general.
One problem is that displays are usually around 60 FPS, and the NES is 60.098FPS, so they'll go out of sync eventually. If the monitor is slower, eventually frames will need to be dropped. If the monitor is faster, eventually frames will need to be repeated.
Initially:
You find out what time it is using a very precise timer. That is your reference time for the first frame.
Your next frame is scheduled to happen 1000/60.098ms later.
In general:
All calculation of the next scheduled timestamp is done relative to the scheduled timestamp of the current frame. Not the current time, but the scheduled time.
But if your time is off by more than 4 frames, you'd need to resynchronize.
When you present/swapbuffers a frame with vsync enabled, it will take around a frame worth of time before the code returns.
After you present:
- Is there time remaining before you would reach the scheduled time? You'll need to wait for the scheduled time to happen.
- Did you already reach the scheduled time? You won't need to wait.
- Is current time more than 2 frametimes past the scheduled time? Drop a frame (skip presenting it)
- Are you more than 4 frametimes away from the scheduled time? You're out of sync, maybe someone put their computer to sleep or something. You'll need to resynchronize, meaning scheduled time becomes relative to Now rather than the past scheduled time.
How to wait on Windows:
Use the Waitable Timers API. Before doing anything else, you create the waitable timer, and increase timer precision (the undocumented function NtSetTimerResolution can set timer resolution in units of 100ns)
Each time you want to wait, you set the target timestamp of the timer, then WaitForSingleObject on the timer.
How to wait on Posix/Linux:
Haven't tried this but I think you get the current time (monotonic), then use nanosleep.
One problem is that displays are usually around 60 FPS, and the NES is 60.098FPS, so they'll go out of sync eventually. If the monitor is slower, eventually frames will need to be dropped. If the monitor is faster, eventually frames will need to be repeated.
Initially:
You find out what time it is using a very precise timer. That is your reference time for the first frame.
Your next frame is scheduled to happen 1000/60.098ms later.
In general:
All calculation of the next scheduled timestamp is done relative to the scheduled timestamp of the current frame. Not the current time, but the scheduled time.
But if your time is off by more than 4 frames, you'd need to resynchronize.
When you present/swapbuffers a frame with vsync enabled, it will take around a frame worth of time before the code returns.
After you present:
- Is there time remaining before you would reach the scheduled time? You'll need to wait for the scheduled time to happen.
- Did you already reach the scheduled time? You won't need to wait.
- Is current time more than 2 frametimes past the scheduled time? Drop a frame (skip presenting it)
- Are you more than 4 frametimes away from the scheduled time? You're out of sync, maybe someone put their computer to sleep or something. You'll need to resynchronize, meaning scheduled time becomes relative to Now rather than the past scheduled time.
How to wait on Windows:
Use the Waitable Timers API. Before doing anything else, you create the waitable timer, and increase timer precision (the undocumented function NtSetTimerResolution can set timer resolution in units of 100ns)
Each time you want to wait, you set the target timestamp of the timer, then WaitForSingleObject on the timer.
How to wait on Posix/Linux:
Haven't tried this but I think you get the current time (monotonic), then use nanosleep.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
Re: SMB skipping frames
Alternatively, since most NES emulators will eventually need to play audio, you can use the technique of synchronizing your speed to audio playback - since you know exactly how fast the CPU is supposed to run, you can have it generate the correct number of audio samples for each set of CPU cycles and then add them to a playback buffer which gradually empties in the background as the sound gets played through your speakers, and whenever that buffer becomes full you can sleep for a few milliseconds and wait until there's enough room available.
One downside to this technique is that it might not work well with options such as VSync.
One downside to this technique is that it might not work well with options such as VSync.
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
P.S. If you don't get this note, let me know and I'll write you another.