Skip to content

Instantly share code, notes, and snippets.

@kg
Last active May 10, 2024 13:08
Show Gist options
  • Save kg/9bba9ec8570e878bfd2e59b6c108259f to your computer and use it in GitHub Desktop.
Save kg/9bba9ec8570e878bfd2e59b6c108259f to your computer and use it in GitHub Desktop.

hi! i'm a game dev and when i watched alex's video about skoe some ideas immediately came to mind re: how the 60fps lock would happen, and how it would drift

i looked at the cocos source in https://github.com/altalk23/cocos2d-x-gd to try and get a sense of how cocos works, though ultimately a lot of this will depend on how GD is written. i also looked at current cocos2d-x on its public repo and most of the key details are the same.

(sorry this is disorganized) i'll try and start by explaining how most game engines (including cocos 2.2.3 from what i can see) work. they usually perform a set of steps in a loop:

  1. before step 1 cocos computes something called 'delta time', a measurement of how long it's been since the last time step 1 happened. various parts of cocos use this delta time to influence updates, potentially this would include gd's physics. cocos' current "FPS" is determined based on this delta time
  2. ask windows 'has anything happened, like keyboard or mouse input?', and keep asking until there is nothing left for windows to tell you. cocos appears to rely on these events to update the state of the keyboard and mouse, it doesn't query the input device directly
    • a key nuance here is that some of these events are "coalesced", where windows is allowed to throw out events that happened since the last time you hit step 1 if there are too many. i don't think this is happening to GD though
  3. now that you've processed events, it's time to prepare the next frame. this means doing things like physics updates, responding to player input (if you didn't do it during step 1), and asking the GPU to draw stuff. cocos appears to do all of its updates before ever talking to the gpu, so:
  4. cocos begins to ask the GPU to draw stuff - it clears the screen, runs drawing code, etc
  5. cocos asks the OS to 'swap buffers', which means (i'm simplifying here) waiting for the gpu to finish drawing stuff, then throwing the result onto the screen.
  6. we now return to 0

there are various ways most of these steps can get screwed up or delayed, which could result in that 60fps lock, and could also cause it to drift. some examples in no particular order. this is nerd bullshit you can skip if you want.

  1. screen capture software can insert a delay at step 3 or step 4, basically locking a game's real FPS to their FPS. this delay can be variable
  2. the OS's "compositor" can insert a delay at step 3 or step 4, basically locking a game's real FPS to the compositor's FPS - which sometimes is the monitor's refresh rate, but not always
    • the compositor tries to be smart and if two or more programs, i.e. GD and the OBS preview window, are sending frames to it at once, it will make a best effort to sync up their framerates, and one of them will potentially be the "loser" and get locked to the winner's frame timing
    • this gets more complex with multiple monitors, especially if one of them is high-hz (i.e. 120, 144) and the other is low (i.e. 60)
  3. the way steps 3 and 4 behave depends on the state of the GPU/compositor and how much work your PC is currently doing. a loose term for this is "back pressure":
    • typically to maintain a smooth framerate and deliver maximum fps, the GPU and compositor will both queue up multiple complete frames, and then show them back to back when the monitor is ready
    • this queueing creates latency, but means that if the game momentarily lags, there will be less likelihood of a visible 'dropped frame' (it will turn into a glitch instead)
    • if this queue fills up, "back pressure" results in step 3 and/or 4 halting until the queue is empty, which means they basically wait for vsync.
    • if GD is running at 60fps, i don't expect this would happen on a monitor with a vsync higher than 60fps. it could if your GPU is too slow.
    • but in scenario 2, discord or the obs preview window could have unintentionally locked the compositor to their frame timing, which could lock GD to it as well via the queue
  4. gsync and freesync make everything unpredictable and horrible
    • it's hard to emphasize just how weird and unpredictable this is. if you have a gsync/freesync monitor you could be experiencing this without noticing it. personally on my machine my combination of monitor and drivers makes me unable to consistently hit vsync in windowed mode, which made me feel insane until i figured it out.
  5. overlays like the discord, steam, or shadowplay overlay also insert themselves into step 3 or 4, which can cause delays
  6. sometimes the video driver or some other part of your system will just make step 3 or 4 take forever. nvidia's drivers have a quirk where they will occasionally decide to lock a specific exe file to 60fps until you eradicate the driver with DDU (yes, really)

to get back to 'how would the 60fps lock happen and how would it drift':

  • i mentioned above that there are lots of things that can cause a delay in step 3 or step 4. cocos will wait and then resume its loop, and get a large delta time - 16ms or more - and basically be running at 60fps.
  • if you've set the internal framerate to i.e. 360fps, cocos will look at that 16ms delta time and potentially run multiple frames back to back to catch up. because this happens entirely in step 2, this means windows will not get asked for events between these frames, so your input is 60fps locked.
  • note that most physics engines do this delta time catch-up thing, and typically physics engines have a higher internal frame rate than 60 anyway - this increases the stability of the physics. but the rest of GD's game logic probably will run at whatever the internal framerate is, governing things like dying from a collision or doing collision detection
  • these step 3/4 delays are unpredictable, and can depend on things like what other programs you have running, whether an overlay is visible, whether a floating window like the OBS chat is on the fullscreen monitor, etc. so your 60fps lock could occasionally drift due to these random pauses
  • if the time between runs of step 1 is long enough, a mouse press and release can both happen at the "same time" - cocos will see the press and immediately see the release in the same frame

one useful tool for investigating this is Intel PresentMon. It can show you charts of how much time is being spent on the CPU and GPU, whether there are frametime spikes, and how much render delay (from queueing) there is.

@SMJSGaming
Copy link

SMJSGaming commented May 10, 2024

To just put it simply

Vsync forces OpenGL to run at the same rate as your monitor refresh rate. OpenGL is also what manages the touch begin and touch ends on your window. If OpenGL is bottlenecked in how often it may cycle, then so will be the inputs, indeed aligning them to the frames. Meanwhile CCScheduler is what determines the game steps, those are cycled seperately and thus may ignore vsync. Inside OpenGL there are 2 separate systems which get rate limited by vsync, but 3rd parties may remove that rate limit of the visual system for their own sake, meanwhile the window input system still operates at the vsynced rate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment