Project

General

Profile

Actions

Emulator Issues #13725

open

OSD can sometimes show incorrect frame count when loading a savestate

Added by InputEvelution about 1 month ago. Updated about 1 month ago.

Status:
New
Priority:
Normal
Assignee:
-
% Done:

0%

Operating system:
N/A
Issue type:
Bug
Milestone:
Regression:
No
Relates to usability:
No
Relates to performance:
No
Easy:
No
Relates to maintainability:
No
Regression start:
Fixed in:

Description

Game Name?

Any game will work for this.

What's the problem? Describe what went wrong.
When loading a savestate, Dolphin's toggleable on-screen display will sometimes display a frame count of 1 more than when the savestate was created, only correcting itself on subsequent frames. This will always happen if emulation is progressed via frame advance directly prior to creating the savestate, a situation very common when TASing.

What steps will reproduce the problem?

  1. Go into the Movie tab and check "Show Frame Counter".
  2. Boot up any game in Dolphin.
  3. Pause emulation, and press the frame advance key at least once.
  4. Create a savestate.
  5. Load the savestate. The displayed frame count will have increased by 1, resulting in an incorrect number.

Is the issue present in the latest development version? For future reference, please also write down the version number of the latest development version.

Yes. 2412-213

Is the issue present in the latest release? For future reference, please also write down the version number of the latest release.

Yes. 2412

What are your PC specifications? (CPU, GPU, Operating System, more)

OS: Windows 11
CPU: Intel Core i5-12500H
GPU: Nvidia Geforce RTX 4060 Laptop GPU

Actions #1

Updated by Dentomologist about 1 month ago

The short version is that the Frame Counter overlay often trails the value of MovieManager::m_current_frame by 1. When you load the savestate after a framestep (or sometimes even if you just paused normally) you're seeing the correct value, but then it starts trailing again after the next framestep for complicated reasons.

Now for the long version.

The numbers below mostly depend on the values of various timing registers and will probably be different between games or even different points of the same game. I'll use values from Super Mario Galaxy 2's title screen as an example, but the idea should hold generally even if the specific values are different.

VideoInterfaceManager::Update() gets called once per half line. Each call does the following in order (with some irrelevant steps omitted):

  • If the half line count equals 0 or GetHalfLinesPerEvenField() (=1050), MovieManager::FrameUpdate() increments m_current_frame.
  • If the half line count is at the first half line of either an odd (=108) or even (=1158) field, it outputs the XFB (some games do this at the last half line of a field instead). After rendering the XFB on screen, OnScreenUI draws any overlays (such as the frame counter, which uses the value of MovieManager::m_current_frame) on top of the image. Note that the XFB itself isn't changed here.
  • If the half line count equals 0 or GetHalfLinesPerEvenField() (=1050) (which are the same times that m_current_frame increments), Core::Callback_NewField() checks to see if a framestep is in progress and has displayed a frame. If so it triggers the emulated CPU to break, pausing emulation.
  • The half line count is incremented. If that makes it equal to GetHalfLinesPerEvenField() + GetHalfLinesPerOddField() (=2100) it wraps to 0 instead.

Some things to note:

  • Pausing (with the button or keybind) causes the half line to end up at an unpredictable value, but will often end up in the range 1-107 or 1051-1157. This means that m_current_frame will have been incremented but the corresponding XFB won't have been displayed yet, meaning you'll see the outdated XFB and frame count.
  • A framestep will always end with the half line at either 1 or GetHalfLinesPerEvenField() + 1 (=1051), and the value of MovieManager::m_current_frame will have just been incremented. In this case the displayed frame count will always be outdated.
  • When creating a save state the current value of m_current_frame will be saved.
  • When loading a state the XFB will be copied to the screen (which doesn't include any overlays), and then redraws the overlays using the values loaded from the savestate. In particular, this means that the immediate XFB presentation when loading a state can use a different value of m_current_frame than was used when the XFB was originally presented, which is the most immediate reason you're seeing different frame counts before and after loading.
  • In the case where a savestate is made from half lines 1-107 or 1051-1157, the XFB will be overlaid with an outdated frame count when you make the save, then redrawn with the correct frame count after loading. Framestepping will cause the next XFB to be drawn at 108 or 1158 with the same framecount, increment m_frame_count at 1050 or 0, then stop at the end of that half line making the displayed frame count inaccurate again.
  • In the case where a savestate is made from lines 109-1049 or 1159-2099 the XFB will have been updated after the corresponding increment of m_current_frame, and so the displayed frame count will be correct both before and after loading. Framestepping will increment m_frame_count at 1050 or 0, but since no frame has been displayed during this framestep yet it will continue stepping. The XFB gets displayed at 1158 or 108 with the correct value, but stepping doesn't stop until 0 or 1050 increments m_frame_count, desynchronizing them again.

So, to summarize all that: the displayed value after a pause may or may not be correct, after loading a savestate it'll be correct, and after framestepping it'll be wrong.

Now the question is what's the best way to deal with that.

  • Moving the call of MovieManager::FrameUpdate() from VideoInterfaceManager::Update() to VideoInterfaceManager::OutputField() would solve the desynching, but without having looked into it I'm guessing that would cause other issues somewhere in the Movie logic since m_frame_count would have the wrong value for 108 half lines per field.
  • If we always do a framestep when pausing so we end up on a consistent half line count, we could just add 1 to the displayed value of the Frame Counter so it'll be accurate once the framestep ends. This has the problem that when loading a savestate would have to make sure that we didn't do that, since the value of m_frame_count used would be 1 higher than normal, which sounds messy. Also, it would prevent features like https://bugs.dolphin-emu.org/issues/13727.
  • I think this is another member of the pile of problems that would be straightforwardly solved with async presentation; since the UI would be redrawn on each present the problem would fix itself automatically.
Actions #2

Updated by JosJuice about 1 month ago

The Movie logic doesn't actually use the frame count for much besides showing it to the user. The only thing I found was some checks for whether a movie only just started, which are used by State.cpp to skip creating DTM files corresponding to savestates when a movie only just started. The real meat of the Movie logic is agnostic to the frame counter. So I don't see any obstacles preventing us from going with the first option you listed, though async presentation would of course be nice to have regardless.

Actions

Also available in: Atom PDF