Nightinggale wrote: Thu Dec 06, 2018 5:34 amSo in essence you are saying it's not a perfectionistic "never lose a frame" fanatic problem, but rather "imperfections in motion gives me motion sickness" kind of problem. So you are saying placing screen updates and game logic in two different threads will be beneficial related to reduce the risk of headache from playing? That sounds interesting.
Indeed it's kind of like a motion sickness, except i never get motion sickness playing any game. But it feels like being punched in the face every time a frame arrives "out of sync" so to speak. A single dropped frame is fine, but when they chain together in such a way that you get wildly varying FPS then it's painful, irritating and demoralizing all at once. As soon as i encounter such an issue i do everything in my power to combat it.
Looking at the Steam FPS counter, it's flickering between 59 and 60 FPS when it's happening. But it's more than that, most FPS counters work on averages.
Let's say it works on an average over a second.
First 500 ms runs at 60 FPS
Second 500 ms runs at 30 FPS
Such a frame counter (1 second averages) would display 45 FPS as that's the average over the whole second, but doesn't say jack about how the game performed in that one second.
Okay, factorio's ingame FPS counter updates every frame. It shows alternating FPS and UPS between 59.9 and 60. Most of the time it's stable at 60 but then every once in a while it starts flickering 59.9 - 60.0 and then goes back to solid 60 again.
I can't say how they count their frame timings for sure but i would assume that the number of milliseconds that passed in the last frame is used.
So if the last frame took 16.666... ms to simulate and draw, including the idle time, then it would display 1000/16.666... = 60.0 FPS.
But if the last frame took 16.694... ms to draw (A difference of a mere 0.0278 ms) then it will display 1000/16.694... = 59.9 FPS.
BUT, even then it doesn't tell the whole story... What happened in reality? This happened...
Frame 1 took 16.666 ms to render. 60 FPS all is good!
Frame 2 took 16.666 ms to render. Still 60 FPS all is good!
Frame 3 took 16.694 ms to render. Oh... The game is running slow now, the frame is out of sync.
Frame 4 took 16.666 ms to render. 60 FPS again but the frame is still out of sync.
Frame 5 took 16.666 ms to render. 60 FPS still, also still out of sync.
Frame 6 took 16.694 ms to render. Oh snap... Game is running slow, the frame is even more out of sync.
If it does that for all 60 frames of a second then my eyes will notice it. Even if vsync is off.
(By the way, if vsync was on it would actually DROP a whole frame waiting for the next sync instead. Frame 3 and 6 wouldn't draw at all and that's even worse when it happens so often.)
So, it's switching between running steady and jagged like that and that's what bothers me. But why would this be?
Let's experiment some with floating point numbers shall we?
Using the limitations of 32 bit floating point values, as in all numbers in the below example have floating point rounding errors in them:
Here's some C# source code
Code: Select all
static void Main(string[] args) {
float frame_step = (1000f / 60);
float last_render = 0;
for (var i = 0; i < 60; i++) {
Console.Write(i);
Console.Write(" : ");
Console.WriteLine(last_render);
last_render = last_render + frame_step;
}
Console.ReadKey();
}
And here's the output
Code: Select all
0 : 0
1 : 16.66667
2 : 33.33333
3 : 50
4 : 66.66666
5 : 83.33333
6 : 99.99999
7 : 116.6667
8 : 133.3333
9 : 150
10 : 166.6667
11 : 183.3333
12 : 200
13 : 216.6667
14 : 233.3334
15 : 250
16 : 266.6667
17 : 283.3333
18 : 300
19 : 316.6667
20 : 333.3333
21 : 350
22 : 366.6666
23 : 383.3333
24 : 399.9999
25 : 416.6666
26 : 433.3333
27 : 449.9999
28 : 466.6666
29 : 483.3332
30 : 499.9999
31 : 516.6666
32 : 533.3333
33 : 549.9999
34 : 566.6666
35 : 583.3333
36 : 600
37 : 616.6667
38 : 633.3334
39 : 650.0001
40 : 666.6667
41 : 683.3334
42 : 700.0001
43 : 716.6668
44 : 733.3335
45 : 750.0002
46 : 766.6669
47 : 783.3336
48 : 800.0002
49 : 816.6669
50 : 833.3336
51 : 850.0003
52 : 866.667
53 : 883.3337
54 : 900.0004
55 : 916.6671
56 : 933.3337
57 : 950.0004
58 : 966.6671
59 : 983.3338
Observe the output and you will find a few irregularities.
Frame index 1 is 16.66667, all sixes expected as decimals.
Frame index 6 is 99.9999, should be an even 100.
Frame index 14 is 233.3334 where most others are x.3333
Frame index 29 is 483.3332
Frame index 39 is 650.0001
Frame index 45 is 750.0002
Frame index 51 is 850.0003
Frame index 54 is 900.0004
If one used that as a frame limiter you would notice it too! The frame times would be all over the place.
So yes, timing does matter and it's not just how well the game is running, it's probably related to rounding errors.
It wouldn't be noticeable if the game rendered more frames than the refresh rate of the monitor.
Which made me think, haven't tested it though... What if i increased the game speed to 1.01 and reduced my monitors refresh rate to 60. The game would run 1% faster but maybe, just maybe i could get rid of the stutterings.
Nightinggale wrote: Thu Dec 06, 2018 5:34 amKlonan mentioned in the video something about the car moving while being drawn was an issue. Is movement of entities the only issue? What would it take to make the double buffering take place in the entity class? (I'm sure there is a base entity class somewhere in the code)
Only BIG movement is an issue, if a drone teleports across the screen that's not an issue. Only when the whole screen moves does it affect me.
Likewise, if you fill the screen with drones and they are all moving in different directions but at 20 FPS, it doesn't affect me. It's single direction movement of large areas of the screen that has an effect. Such as moving the camera or moving the character (which is the same as moving the camera)
If the camera keeps moving at a fixed rate then no probs.
Nightinggale wrote: Thu Dec 06, 2018 5:34 amWhat data would be needed. It would be something like x, y, direction, sprite. Seems doable, particularly if sprite can be reduced to an int or pointer. Now imagine adding a class with those and add two instances in the entity class. Let's call them container 0 and 1.
Now the one being used is tick % 2. Say we calculate tick 3. 3 % 2 = 1. This means this tick we use container 1. The first thing we do in tick() is to copy container 0 into 1. After that we run the entire code normally where the access functions goes into container 1. Next tick it will copy container 1 into 0 and then all access functions will use container 0. This should provide the same game logic as right now. The reason why it's a class is it allows copying all of them in one go, which is faster than adding a line for each variable being copied.
The trick here is to make the screen "lag one tick behind". While calculating tick 3, it will draw tick 2. This means while it's updating it will draw container 0. All entities will have fixed container 0, meaning the screen drawing code will view unchanging data while drawing. This should remove the need to sync the screen drawing code with the game logic, hence allowing hundreds of FPS if some future GPU can handle it while maintaining 60 UPS.
Perhaps it needs some variable to determine if tick() has been called, like just storing 0 or 1 when copying the container. This way even if it will copy to container 1, if some other entity use a get function prior to tick(), it will read from 0, which contains the result from the last tick. Container 1 would in that case contain the data from 2 ticks ago, hence outdated.
There is one issue though, but I don't know if it's an issue or not. What if it draws 10% from frame 2, the game logic finishes tick 3 and starts tick 4. What will happen? Will there be a frame, which becomes a mix of two ticks? Could something be done to prevent this from being an issue?
I can't answer any of those questions because screen updates is outside my field of expertise. I just point to them as potential issues from a data integrity point of view.
I'm sorry to say but you lost me there. I was talking about double buffering the whole simulation. Reading from one page, writing to the other.
There's no real copying taking place.
But now i realize my error... DAMN.
Double buffering works fine on rendering because the WHOLE screen is being redrawn. Whereas double buffering the simulation would mean you are writing to data that is 2 frames old.
Maybe i get what you mean now.
Either way, i don't really need double buffered simulation to be happy. All i need is that the camera is FREE of the simulation so when it moves it can do so without waiting for the simulation to finish.
Like i said before, if the renderer ran at 120 fps all would be good, the issue wouldn't be so noticeable then. The smaller the frame steps, the less noticable a single frame drop or desync is. If we had 1000 FPS this wouldn't have been an issue to anyone. The myelinated nerves in human eyes can fire 1000 times per second. Some peoples eyes are slower of course, hence they can deal with 60 and below FPS just fine.
I am "blessed" with fast firing myelinated nerves or something. I haven't actually tested my eyes, but i tell it like i see it.