Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Engine update thread #8

Closed
wenq1 opened this issue Dec 22, 2016 · 14 comments
Closed

Engine update thread #8

wenq1 opened this issue Dec 22, 2016 · 14 comments

Comments

@wenq1
Copy link

wenq1 commented Dec 22, 2016

Hi Elnormous, Great work on your multi-platform support!

I have a question tho regarding Engine::run (). Won't it be hogging the CPU as it is not event-triggered (blocking mode) nor has it sleep ()?

@danielytics
Copy link

@wenq1 this is typical in games. Usually you want to use all available CPU because you're busy updating graphics, physics, ai, game logic and so on.

Having said that, it might be a good addition to provide this as an option in case you want to enable sleeping to lower power consumption and make a non-fullscreen game play nicer with the rest of the system.

@wenq1
Copy link
Author

wenq1 commented Jan 15, 2017

@danielytics Not on mobile I believe. Busy loops means a huge deal for battery life. Nowadays even eglSwapBuffers () are vsync-ed to avoid extensive battery usage.

@danielytics
Copy link

@wenq1 ah, of course, its much more important for mobile. I wasn't thinking clearly ;-)

Does EGL vsync for battery conservation purposes, rather than to avoid visual tearing?

@wenq1
Copy link
Author

wenq1 commented Jan 16, 2017

Visual tearing will never occur on double-buffer backed systems. Reading and writing are always on complete buffers, never on buffers in progress. Performance-wise lockless double buffers are invented to address the issue.

Question asked here is of another scope. When you have a thread spawned (especially on mobile devices), you almost always need a handler based pattern to block somewhere, or you manually insert sleeps between loop iterations. Otherwise batteries will be tortured or considered fully utilized as a cookware.

@danielytics
Copy link

You still need to wait for vsync before swapping the buffers to avoid tearing. Otherwise you may still have part of the screen rendered from one buffer and the rest from another. My question was whether vsync is also done because of battery, or if thats just a happy side-effect. Probably a mixture of the two. Anyway, I've taken this ticket off topic enough.. apologies!

Sure, that makes sense and I guess that's why mobile apps are inherently event-based (doing little or nothing until the user does something). Could ouzel perhaps get a little closer to this by providing configurable fixed timesteps (eg so that the developer can decide how often rendering (and ideally independently, game logic) updates are run)? Eg, if you could say "render at 60 Hz and update game logic at 15 Hz". Not quite event-triggered of course, but still a lot more control over battery use than currently. Maybe I'm talking BS though ;-)

@elnormous
Copy link
Owner

There is no way to reliably fix the update thread to 15 Hz. I was considering that (and if you look at the commit history, it was implemented 2 years ago) a fixed update with sleeps, but sleeps are unreliable. They guarantee a minimum amount of time to sleep, but not maximum. For example, my game is updating all the logic at 60 Hz. I tried to add a sleep to force the update thread to run at 100 Hz and I got inconsistent update rate, that affected my game dramatically. Syncing the update thread with the render thread (vsync) is also a no-go, because every device can have its own refresh rate and usually it is not precisely 60 (I got 60.1 on some and 59.9 on others). If you are updating your physics exactly 60 times a second and you get a refresh rate that is something around 60, you will get have a double-update once in a while and that makes the game feel really glitchy.

@wenq1
Copy link
Author

wenq1 commented Jan 16, 2017

"Otherwise you may still have part of the screen rendered from one buffer and the rest from another" - No that should never happen in double buffered systems, Otherwise either locks screwed up on single-buffered system or poor logic is in charge in multi-buffer backed systems.

Interesting read here: http://stackoverflow.com/questions/23666069/single-producer-single-consumer-data-structure-with-double-buffer-in-c

Completely off-topic above. Ignore me..

Fixed step rendering is .... yet another thing. Check this out: http://gafferongames.com/game-physics/fix-your-timestep/. It is a game-loop design pattern that makes physics more predictable, irrelevant of your implementation.

Ouzel chooses to use multithreading to implement game update part of the rendering. It is the cpu-hogging behaviour itself that incurred the question.

@danielytics
Copy link

danielytics commented Jan 16, 2017

I was thinking something like this http://gafferongames.com/game-physics/fix-your-timestep/ where it makes a best effort to fix the timestep, but also allows multiple updates when enough "missed time" is accumulated. They did it for the purpose of physics, maybe its not suitable here. I know that getting it to be exactly X Hz is impossible...

EDIT:
hah, posted the same link at almost the same time :D

No that should never happen in double buffered systems

Do you mean with hardware double buffering? Presumably that's because they will always wait for vsync, no?

If software double buffering is used (which, granted, in this day and age shouldn't be necessary), you still need to synchronise: when you SwapBuffers the screen might be halfway through the screen and it will start pushing pixels to the screen. The top half of the screen will still show the previous frames graphics. It would be corrected next time the screen starts from pixel 0, but unless you have some way of making sure every pixel was read from the buffer before swapping, you could end up with the same situation again and again. Even the wikipedia page on multi-buffering says that modern systems synchronise between swaps. (Going by your SO link: I'm not saying that "you" are drawing part into one buffer and part into the other; I'm saying that the GPU is drawing part of one buffer to the screen and part of the other buffer to the screen, causing the screen to contain a mix of buffers; the buffers themselves may be properly locked and this could still happen).

I would expect this to all be happening in hardware, but at least with my experience with OpenGL, when I set it up with double buffering, I still have to tell it I want vsync for it to use vsync and if I do not, I get tearing. (Maybe ES always does vsync, I have no experience with ES)

@elnormous
Copy link
Owner

Yes, I have seen this article quoted many times and I have implemented a similar logic in my game, but it doesn't answer the question, how to fix the update rate of the game thread to the one the user wants (e.g. 15 Hz). As I mentioned above, using sleep is not an option. That is why I created a while loop and user can do whatever he wants in it. He can add an update callback to the engine and put a sleep in it if he wants to save resources. But I don't think this is something that has to go into the engine. Maybe you have better ideas?

@danielytics
Copy link

danielytics commented Jan 16, 2017

@elnormous Am I correct in saying that the problem you hit was that its impossible to remove jitter?

either

  • slow frames mean you get lower Hz

or

  • you do what the article does and it means that you might get two updates in one "tick" when enough extra time has been accumulated?

There's obviously no way to guarantee that the timestep will always be exactly what you want, but if I read the article correctly (but maybe I didn't!) it sounds like the idea is to make sure that the same number of updates happen in a given timeframe. That is, you might not get 1 update every 16.6ms, but you will get exactly two in 33.2ms (more or less). Best effort kinda thing. But I'm not experienced enough to discuss beyond this. You have more experience than I do, so if it didn't work, then...

EDIT: Reading comprehension failure on my part, sorry! You already said:

you will get have a double-update once in a while and that makes the game feel really glitchy

How do you handle it in your game? I assume you're not using a physics engine that requires a fixed timestep. What happens if the framerate drops really low? Even if using purely time-based update logic, there will be jumps in this case. Are they less "glitchy" than with double-updates in the fixed timestep approach?

@wenq1
Copy link
Author

wenq1 commented Jan 16, 2017

Say if you want 15hz update (66ms intervals), sleeping 80ms will be ok, and then just update (66ms) at a time. When deviations accumulates, say, you have slept for 3 times, you update once more at the last sleep. You'll get jitter surely.

Sleeping less, for example 10ms will give you smoother outcome and less jitter. Never sleep at close to the update rate or otherwise you might or might not get the extra update at some point in time.

To overcome the jitter issue, sleep a variable amount of time depending on the designed next (fraction of) frame time, say 60fps. This gives most predictable results when drawing occurs at a constant rate, say vsynced. When the framerate drops, the frame time will depart from the design, but with multi-threaded CPU-side updating will this ever happen?

@zerodarkzone
Copy link

Hi,
you could read this thread from the cocos2d-x forum which is related
http://discuss.cocos2d-x.org/t/cocos2d-x-windows-performance-issues-examined/27915
Leaving a game use 100% of the CPU is not good from my point of view, you're killing mobile and laptop batteries. Lots of game engines offer a kind of fixed update rate so even if it is difficult it is not impossible to implement it.

@elnormous
Copy link
Owner

I will add an option to run the game thread in one of the two modes:

  1. Run as often as possible (current way)
  2. Run only once per v-sync

@elnormous
Copy link
Owner

I added a oneUpdatePerFrame (the name could change) attribute (false by default) to the Engine class. Now the game thread will sleep until the next frame (so it will not run on 100% anymore).
f2f29de

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

No branches or pull requests

4 participants