• Hello [name]! Thanks for joining the GMC. Before making any posts in the Tech Support forum, can we suggest you read the forum rules? These are simple guidelines that we ask you to follow so that you can get the best help possible for your issue.

 Suggestion: Independent 'Step' & 'Draw' Rates

Pudsy

Member
SUGGESTION IN BRIEF
I'd like to see GMS allow us to define separate "rates" for each of: update (Step) & rendering (Draw) events.

I first raised this suggestion in this other thread, which began with suggested improvements to aid delta-timing updates (re: integration), then evolved into a more general discussion of various related situations (delta timing, tweened rendering) & possible ways in which GMS could better support those situations.

@Mike was involved & open to suggestions along those lines, if they were "100%" consistent/complete, robust enough not to cause anyone any issues.
( @Mike I've included your previous comments further down )

My suggestion was evolving in that topic, but I was getting too wordy & didn't want to further derail the thread, so I've split it into this separate topic & will attempt to condense & clarify the suggestion.

Please contribute with any other thoughts / concerns / requirements you may have, or anything I've overlooked.
Thanks!
---


USE CASES
This would be useful for various projects/situations, including:-
  • rendering at any fps to suit target device/platform, but with a different fixed-rate logic/physics/etc.
  • tweened rendering (simpler alternative to delta-timing, fewer issues re: physics, collisions, etc.)
  • running eg. physics at a higher (or lower!) simulation rate than the rendering rate

DRAWBACKS TO CURRENT SOLUTIONS
While solutions to the above are currently possible in GMS, they mostly have the following drawbacks:-
  • "game speed" must be set artificially high, so that...
  • we do our own timing, skipping Draw events as needed to allow Step events to execute at desired rate
  • results in extra CPU load, with a high-rate timing loop needlessly sitting on top of GMS's perfectly good timing loop, making it harder for GMS to sleep() when it otherwise could
  • many GMS built-in instance variables & related features become meaningless since they assume 1 Step per Draw (eg. speeds, physics, animation, alarms, paths, timelines, etc.)
  • so we end up with our own custom instance variables for those things, and hacked workarounds
  • the runner/compiled game thus wastes resources maintaining all those built-in properties that we can't use, or worse, that we have to undo/redo

POSSIBLE IMPLEMENTATION
  • Keep current "game speed" ie. rate at which Step events (including Begin,End) are triggered.
  • A new "draw speed" would control the rate at which Draw events (Begin,End,GUI,etc) are triggered.
  • "draw speed" can be defined in project settings (as "Game Frames Per Second" already is).
  • "draw speed" can also be set/get via GML functions, eg. draw_set_speed() & draw_get_speed()
  • "draw speed" defaults to 0 (zero), which would mean you always get 1 Step, 1 Draw, repeating forever. This ensures backwards compatibility by maintaining current behaviour when loading/importing old projects.
  • Any other value for "draw speed" acts as a maximum limit on the rate of Draw events.
  • So, you could set it to eg. 30 or 60 for mobile, or 9999 for max performance on a desktop (with vsync enabled or disabled), or allow the player to set it via the game's options, in order to cap the framerate.

POTENTIAL ISSUES / MIKE'S PREVIOUS COMMENTS
I quite like the idea of a different FPS for the draw event, that's not actually that hard. I suspect it would have to be a multiplier of the step (or other way round - whatever). We do get requests of more steps to the draw, so things like physics can be run at a higher rate. There's no point in running graphics faster than 60fps (currently) as although there are the new GSync monitors, they are rare and aren't really catching on.

The problem would be that if it wasn't, then eventually one of the events would suffer a delay due to the other one over running. For example, an FPS of 23 for step and 60 for rendering. This would mean than the drawing would at some point be delayed if the step needed to be run just before, and this could cause stuttering anyway.

When they are tied together (as they are now), devs take this into account - even when they don't realise they're doing it, but with a more free form speed, that's a harder thing to grasp and we'll get constant reports of stuttering and tearing.

If I were doing this in native code, I'd run each process on a separate thread, then pass over "new" drawing locations each step event. then the drawing code could tween nicely itself, and the process event would never hamper the drawing on as it was on a separate thread (assuming at least dual core). But that's currently not possible in GMS - and a WHOLE different conversation.
Thanks again for looking over the suggestion in that other thread Mike. Points taken on board! Here is a remix of my thoughts from the other thread, plus some new ones...
  • I agree that separate threads is probably beyond what we would currently expect/require from GMS.
  • More simply, each time GMS goes around the Step/Draw timing loop, instead of doing both, it would evaluate which timer is due to trigger first ("Step" or "Draw"), and do so.
  • This may very well result in some of those events being triggered slightly late, but that is no different to the current situation where a Step event could over-run and delay the next Draw, resulting in slowdown/stutter.
  • If a Draw event is late/missed, GMS would not try to "catch up", it would just trigger one at the next opportunity. It would obviously still continue triggering Step events at their independent rate, rather than waiting to fit in any missed Draw events. The "draw_speed" would simply act as an upper limit (as the "game speed" currently does).
  • Remember the default behaviour would be (draw_speed=0) a backwards-compatibility mode, where you ALWAYS get 1 Step, 1 Draw, repeat forever. No timing conflicts, no stutters because events triggered out of sequence, and no code being missed because someone put game logic in the Draw event.
  • Beyond that, without going into the level of detail I did on the other thread, I think if a user of GMS specifically sets the Step & Draw rates to different numbers (which may not be nice factors/multiples!), then we have to assume that developer has done so intentionally, and trust them to "tween" (or otherwise smooth out) their motion/rendering themselves. After all, that would be the intention of setting the values in that way.
  • I agree that some of the other use cases (with nice multiples) are much simpler to grasp...
    • eg. game_speed=120 (high-precision physics), draw_speed=60
    • eg. game_speed=60, draw_speed=switched 30(low-spec/mobile) or 60/90/120+(high-spec/desktop)
  • I'm not sure what the takeup of >60Hz display is, but a side effect of independent Step/Draw rates would be that we can more easily support smoother motion on high-refresh-rate monitors (while keeping the same fixed rate game logic, and retaining access to built-in GMS instance vars & functionality), so that has to be a plus for both GMS & any games made with it.
 
Last edited:
Top