Post

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++;
}
Esta postagem está licenciada sob CC BY 4.0 pelo autor.