Como Fixar o Frame Rate do Jogo
Opção 1
O frame-rate é importante para que possamos sincronizar nosso jogo com a taxa de quadros do monitor (hz).
O ideal seria descobrir a taxa disponível como 120hz, 90hz ou 60hz e trabalhar com o jogo em cima dessas taxas.
1
2
3
4
5
6
7
// aqui devemos usar funções para descobrir a taxa do monitor
// por enquanto vamos fixar em 60hz
int monitor_refresh_hz = 60;
// o jogo deve rodar a metade do frame-rate do monitor
// porque estaremos usando software renderer
int game_update_hz = monitor_refresh_hz / 2;
float target_seconds_per_frame = 1.0f / (float) game_update_hz;
Hertz é a medida de ciclos por segundo que corresponde também aos frames por segundos.
Vamos fazer o cálculo para manter em um looping interno consumindo tempo até atingir o tempo esperado de 60/30FPS por exemplo.
1
2
3
4
5
6
7
8
9
10
11
LARGE_INTEGER end_counter = get_ticks();
f32 seconds_elapsed = get_seconds_elapsed(last_counter, end_counter);
// aqui provavalmente já foi muito rápido. logo, vamos esperar o tempo consumindo
// dentro de um while até atingir o target de 1 / 60 seconds per frame.
while(seconds_elapsed < target_seconds_per_frame) {
LARGE_INTEGER middle_counter = get_ticks();
// atribui o novo valor para aumentar o seconds_elapsed e sair do looping
// quando necessário.
seconds_elapsed = get_seconds_elapsed(last_counter, middle_counter);
}
NOTE: Também devemos usar
Sleep
para que o jogo não use muito processador desnecessariamente.
Opção 2
Há outra maneira de fazer.
Podemos inverter a lógica onde, enquanto o tempo for maior que o tempo alvo, isto é, enquanto for maior de 1/60 seconds per frame, ficamos no looping forçando update()
e já marcamos um boolean como verdadeiro para caso consigamos sair do loop, podemos renderizar a tela.
Ou seja, queremos apenas 1 render por frame, sendo que se demorar muito, vamos ficar no update()
até retornar ao frame-rate esperado.
Para isso, precisaramos de um acumulador do tempo já passado: delta_elapsed
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
f32 delta_elapsed = 0.0f;
f32 time_counter = 0.0f;
u32 counter = 0;
u32 frames = 0;
LARGE_INTEGER work_counter = get_ticks();
f32 seconds_elapsed = get_seconds_elapsed(last_counter, work_counter);
delta_elapsed += seconds_elapsed;
bool tick = false;
while(delta_elapsed > target_seconds_per_frame) { // passou 16ms
// update();
LARGE_INTEGER check_counter = get_ticks();
time_counter += target_seconds_per_frame; // adiciona 1 frame a conta (16ms)
delta_elapsed -= target_seconds_per_frame; // remove o tempo para tentar sair do looping
tick = true; // libera render
counter++; // somador de frames (16ms)
if (counter % 60 == 0) { // contabiliza quando chegar 1 segundo
char timing_buffer[256];
// tempo total / contador total(frame) == segundos por frame
_snprintf_s(timing_buffer, sizeof(timing_buffer),
"ms/f: %f, fps: %d\n",
1000.0f * (time_counter / (f32) counter), frames);
OutputDebugStringA(timing_buffer);
delta_elapsed = 0.0;
frames = 0;
}
}
// SE não passou o target_seconds_per_frame (16ms) então não podemos
// renderizar. Isso quer dizer que estamos bem rápido e nenhum frame
// pode ser feito ainda.
// SE passou o target_seconds_per_frame (16ms) então faremos um update
// e liberaremos para fazer o render neste frame.
// Vamos manter essa lógica até conseguirmos os 60 frames = 1/60 seconds per frame.
// Se passou muito do target_seconds_per_frame (16ms) então ficaremos
// no looping anterior até o delta ser menor que o target_seconds_per_frame,
// liberando nosso render.
if (tick) {
Win32_Window_Size size = win32_get_window_size(window);
win32_update_window(&g_back_buffer, context, size.width, size.height);
frames++;
}