Extreme Framerates

Started by
15 comments, last by tonemgub 10 years, 7 months ago

So I sort of just figured something out... Well, earlier I was kind of being ambiguous when I said "events & rendering" ... what I really meant was "(events+processing/updating) & rendering"

I just figured out, the "old" way where I have processing and rendering in the same thread, and say i restrict to 5 fps... the movement will only happen if the key is held down at the 5 FPS update moment... If you tap the key a bunch of times it basically only registers once or twice - when you hit it at the perfect moment. But, if I put "events+processing" into a separate thread than "rendering", i can still render at 5 FPS.. except movement happens regardless. I can tap the key a bunch of times and it registers and moves forward every time. So - yes, separating "events" from "processing+rendering" is pointless because the events are queue. Same reason separating "events" from "processing" is also pointless. But separating "events+processing" from "rendering" is definitely worth it. :)

Advertisement


The main problem being that it is not synchronized with your in-game timer.
Out of interest (I've never done this) how do you deal with this issue tonemgub?

Is it possible to measure how far in the past an event occurred? From what I can tell, you can only measure the elapsed time between two different inputs.

That's great for cases where a key is pressed and released on the same frame (which happens and would be very important at 1fps, but is rare at 60fps), but when rendering, there doesn't seem to be a way to determine that, e.g. "this key was pressed 10ms before the render function"...

I once a few years ago experimented a bit and looked at what times I got from windows xp for the windows messages and IIRC they were near identical to what I got from GetTickCount() with a little variance.

It would be more accurate (and frame-rate independent) to use the event's real time.

And no matter how much you time things trying to account for different hardware latencies, you will not be able to always get the constant frame rate that you plan for, so basing your event handler on frame-times is always a bad idea IMO.

While it is correct not to work based on frame times, 2 points are that you are not actually trying to account for hardware latencies and the event’s real time is not as important as the time between events.
For point #1, your input system’s goal is to account for latency more on a psychological level. If you are playing Guitar Hero and for whatever reason a frame lasts 1 second, the player has already mentally timed the next few button presses and can still hear the music to aid in his or her timing.
Your goal is to make sure that as long as the player hits the buttons at the correct times in real life, the game handles it as the correct timing of the buttons.
This is almost what you said, but the problem is in how you have implemented it, which I will explain below.

For point #2, the only thing that really matters is how much time has elapsed since each event, so it doesn’t matter if it is the event’s “real” time or a time based on your own arbitrary system, as long as the same delta times can be derived. This is important for knowing why GetMessageTime() is not the best solution inside a more fully matured game architecture. As will be explained below.

I once a few years ago experimented a bit and looked at what times I got from windows xp for the windows messages and IIRC they were near identical to what I got from GetTickCount() with a little variance.

GetTickCount() is extremely unsuitable for games for a myriad of reasons. It has a very large range of drift (up to 55 milliseconds) for one. Google has many other reasons.
http://www.garagegames.com/community/forums/viewthread/11901
http://www.mvps.org/directx/articles/selecting_timer_functions.htm
http://randomascii.wordpress.com/2013/05/09/timegettime-versus-gettickcount/ (note that he starts off with consistent results due to problems in the test code, which he explains later and ends up once again showing heavy inconsistencies with GetTickCount()).

Microsoft® even recommends never using GetTickCount() and instead using GetTickCount64() to avoid wrap-around issues. Then there is the fact that it is at best accurate to a millisecond, when it is fairly standard to use microseconds in game timers these days.

If GetMessageTime() returns values similar to GetTickCount() then it is even more major reason not to use GetMessageTime().

Ultimately, there is no part of any of these timer function would we ever want to touch our game code.



So what is the solution?

I already explained it in detail here, but I will try explaining it again in context more directly related to this thread.

Firstly, we have already established that we don’t want to touch the above-mentioned timers.
You will always want to use QueryPerformanceCounter() for accuracy, and in modern games you want microsecond resolution.
You will want to make your own timer class and it should always count up from 0 to avoid wrap-around issues. Again, I have gone into great detail on this as well. Although that was for iOS, it is easy to port to use QueryPerformanceCounter() instead.

You will notice that the first thing that class does it get the real time. It uses this to determine delta times between calls to Update(), starting at delta 0.
By copying that delta time to another timer object they are effectively synchronized.

This means you keep the main game timer, updated once per logical tick (a second timer is used for rendering and updated once every frame, and it acts as a slave to the main game timer), on the game thread and on the window thread (where you catch input events from the operating system) you have a synchronized timer to use for time-stamping, with microsecond resolution, input events.
That thread can avoid hogging resources by sitting in WaitMessage() the whole time.
Even if there is some latency before the thread is activated to catch the message and time-stamp it, it will unlikely be worse than the value returned by GetMessageTime()—it should be accurate to less than a millisecond.

The inputs are time-stamped via your custom timer and put into a thread-safe queue waiting to be read by the game.


Now you have inputs time-stamped more accurately and synchronized to a time system that is more appropriate for games.



Now your game enters the extreme worst case of 1 FPS.
If the logical update rate is 30 FPS, after 1 second of lag, it will perform 30 logical updates, each accounting for 33.3333 milliseconds of game time.

Each logical update should then read and process only 33.3333 milliseconds of input.
In this way, no matter what the situation is in the game world, input is read and handled consistently regardless of the FPS.


The difference between this and what tonemgub described is in the foundation.
You can’t use the above-mentioned Windows® timer functions for games at all, and for other reasons mature frameworks will need a custom timer system anyway.
Once you have that in place you have to create your own method for time-stamping events because you can’t synchronize your timers to the Windows® timers reliably (not that you would want to).

And once you stop using the Windows® timers and start time-stamping manually, your time-stamps can only be as accurate as the time you catch MK_LBUTTON, so you have to move your game to another thread and let the main thread do nothing but catch events as quickly as possible.
You can keep the main thread on a high priority so that when it is signaled to catch an event it will not be blocked by your game treads. While it is waiting for events it will be in a waiting state so its high priority will not interfere with the game thread.


The accurateness of the results of the player’s inputs is far more important to the player than visible latency, although of course we prefer to keep that down if possible.


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

it doesn't make sense to render with a higher frequency than your monitor shows,

Imagine that you render a pong ball moving on the screen

from eft to right (say a 1000 pixels) in the time of 1/10 second (this is fast but I think quite realistic physical speed)

Say you render it with 100 Hz, if so you render it ten times

when it moves from left to right with 100 pixel gaps. So

physically it is not a movement but series of jumpings /

teleportations - I doubt that eye interpolates that so I think

this popular belief that 30 fps or somethink like that is sufficient is not quite true (not precise)

if you will physically render it at 1000 Hzimo it should be

better - you could also maybe not render a white dot but

result of some optical suming of light in movement between

frame to frame (this is along of its 100 pixel way) - but this is

I think somewhat hard to do correctly (because it probably

should take into an account some physical characteristics

of display - as this is some precise physical - optical work)

what with that? (this I wrote is an result of my thinking about

it done a couple a years ago, )

fir, yes but Krypt0n is talking about the physical limit of the actual monitor hardware. Rendering unsynced to refresh rate will result in screen tearing... If your monitor had a refresh rate of 1000 Hz, then by all means - render at 1000 Hz. But, it doesn't.

fir, yes but Krypt0n is talking about the physical limit of the actual monitor hardware. Rendering unsynced to refresh rate will result in screen tearing... If your monitor had a refresh rate of 1000 Hz, then by all means - render at 1000 Hz. But, it doesn't.

Sure, but I was saying about topic if 30 or 60 hz is sufficient or not (did not read the rest of the thread, Im tired)

In this example of course I forgot to add it will be worse if you

will render at 50 Hz then you got only five frames and 200 pixel length teleportation jumps.

Did you try to render something like line or fat line, some like a clock indicator or radar line, rounding fast 0-360 degrees in loop. How it look like for you. I was experimenting like that and it was looking like a 5 or 6 (in general n) arm unstable spider, which arms were somewhat fluorescent - I am not sure the cause of it, I used crt monitor set at 85 Hz and winapi for rendering, and rendering was probably at 500 - 1000 Hz not sure now.

how to render correctly such thing ? (not obtaining fluorescent spider) - in real life you will not get fluorescent unstable spider - I think it would be probably like nice glowing (I was using blue line) propeller)

Interesting things was also about mouse cursor (indeed as i was saying already somewhere on my win xp the mouse cursor is the only element which responses ok in the whole this luggish system)

Also the input delay remarks are interesting (not read all) - sad is that i cannot precisely measure it but when i press arow down to roll down webpage content in opera I see some lag - i would say this is aboul 100 ms maybe - this is an internet lag scale and this is sad)

@Hodgman: L. Spiro seems to have answered the question you asked.

I'll only add that it is possible to synchronise GetMessageTime and QueryPerformanceTimer on a single thread, and I do it by using the MsgWaitForMultipleObjects function inside the message loop.

I don't have the code in front of me now, and I can't remember the exact math behind it, but I currently use MsgWaitForMultipleObjects to time both frames and inputs. First, I compute the dwMilliseconds parameter so that MsgWaitForMultipleObjects waits until the next frame needs to be drawn minus one millisecond (or was it 15? IIRC, I use the smallest value that timeBeginPeriod accepts, minus 1). timeBeginPeriod affects the resolution of all the windows apis, except the performance-timers - it even affects the resolution of MsgWaitForMultipleObjects.

Anyway, when MsgWaitForMultipleObjects returns because it reached this wait-timeout limit, then I use QueryPerformanceCounter in a loop to synchronize the thread to the current frame time (which I compute using QueryPerformanceCounter - this is my main game timer) - this will consume that remaining 1 millisecond (or 15) that I subtracted from the call to MsgWaitForMultipleObjects. After that, all I do is Draw and Present the scene using the currently computed game state.

If however, MsgWaitForMultipleObjects returns because it detected a message being added to the message queue, then I do the regular GetMessage(or PeekMessage))/TranslateMessage/DispatchMessage stuff, and if there are any input messages, I re-compute the game state based on GetMessageTime, and here I also do a check to make sure that GetMessageTime is behind my main QueryPerformanceCounter-based timer - if it's not, then I just use the value of QueryPerformanceCounter instead.

Now, the reason Microsoft doesn't recommend using GetTickcount (or other GetMessageTime-like apis) is because of the timeBeginPeriod - once called, it affects all running processes. By default, it has a frequency of ~ 15 ms, but if another process calls timeBeginPeriod(1), this will be used everywhere i nthe system, even for all of the thread-synchronisation APIs, but if I use timeBeginPeriod myself, then I can be sure that it's precision is the one I set - if another process changes it, then it will probably be a video game that changes it to the same value as I need (the smallest period reported by timeGetDevCaps) - but to make sure, I could also jsut call it every time I enter my message loop, before MsgWaitForMultipleObjects, and then also call timeEndPeriod after MsgWaitForMultipleObjects, or at the end of the message loop (since Microsoft recommend this) - this will keep the call to MsgWaitForMultipleObjects in high-precision mode, while not affecting the rest of the system (too much - ok, maybe it does affect it, but I don't care smile.png).

Now, about using a separate thread for timing input events - even then you have to implement some kind of sync for accessing that "input queue" that L. Spiro mentioned, and you are going to be doing this using one of the thread-sync'ing apis or objects (critical sections, events, mutexes, etc.) - but as I mentioned, unless you use timeBeginPeriod, these apis will all be in low-precision mode (or whatever precision was set by other processes), so you are still basically affected by the GetMessageTime "delay-effect" when switching between the input thread and the rendering thread... AndI think the basic message-queue processing apis GetMessage/PeekMessage are also affected, so even if you do use QueryPerformanceCounter, your input timer is still being delayed by the GetMessageTime "delay-effect".

And of course, if you use DirectInput/XInput with Event objects, the same low-precision affects the time when your Event gets signaled (it will only get signaled at a 15ms-boundary). But if you use DirectInput/XInput by Polling (maybe in a separate thread), then you're not afected.

NOTE: As expected, I still have an issue when using VSYNC with this method (but then again, VSYNC would delay ANY timing method that runs in the same thread), since I'm also doing my own frame-sync'ing, and VSYNC will interfere with my own timing - I'm currently looking for a way to get the VSYNC-time to plug-it in into my method, but if there isn't one, I think I can still use this method by always passing a 1ms timeout to MsgWaitForMultipleObjects, and move the scene-Draw part such that I can rely on DirectX's Present method to either "wait for the next vsync and Present" or "discard the current Present if nothing new was Drawn since the last Present". I've already tested this, and it adds at most a 15% CPU-use overhead, whereas the original frame-sync method does a near-0% CPU use (as shown in Task Manager smile.png ). Ideally, the time-out value passed to MsgWaitForMultipleObjects should be the smallest common-denominator between the VSYNC (monitor refresh) rate and the "minimum period" returned by timeGetDevCaps.

Note also that by "re-calculating the game-state" above, I mean simply re-calculating all of the movement-vectors and positions of objects affected by user input, as if they happen at the time returned by GetMessageTme. My project doesn't have that many user-controlled objects currently, so this can be done without exeeding the frame-time limit, so it doesn't cause problems like spikes in FPS or anything. For objects that move by themselves, without need for user input, I still calculate their movement at every frame (right after MsgWaitForMultipleObjects returns WAIT_TIMETOUT and just before starting the QueryPerformanceCounter frame-sync'ing loop). Calculations for collision detection and other object-to-object interractions are done whenever the state of any type of object changes (optimised, based on what type of object it is), so I can't use PhysX or other physics engines that rely on fixed timesteps, but if I had to, I would probably just plug-in the PhysX timestep somewhere right after the QueryPerformanceCounter frame-sync'ing loop.

This topic is closed to new replies.

Advertisement