Desenhando no Back Buffer
No artigo passado alocamos memória através do CreateDIBsection. Contudo, não usaremos mais ele.
Passo a passo
- Alocar memória com base em largura x altura x bytes por pixel.
- Armazenar em variável global as propriedades do bitmap.
- Stretch o tamanho do bitmap ao tamanho da janela.
- Atribuindo valores aos pixels dentro do loop.
- Extrair para uma função de lógica onde será preenchido os pixels (podemos dizer que é a função update() do jogo)
- No MainLoop atualiza constantemente os dados e renderiza (copy) o buffer.
Resumindo: cria a Janela -> tenta ler mensagens de input -> atualiza o bitmap -> renderiza o bitmap com os novos dados -> retorna a leitura de mensagens.
Bibliotecas comuns para inteiros
Para padronizar os inteiros, vamos usar a lib #include <stdint.h>
e definir nossos próprios tipos.
1
2
3
4
5
6
7
8
9
10
11
#include <stdint.h>
typedef int8_t int8;
typedef int16_t int16;
typedef int32_t int32;
typedef int64_t int64;
typedef uint8_t uint8;
typedef uint16_t uint16;
typedef uint32_t uint32;
typedef uint64_t uint64;
Vamos alocar espaço de memória com o VirtualAlloc
. Dessa forma, o Windows irá se encarregar de alocar no espaço físico. Por padrão a alocação acontece por páginas de 4096 bytes no mínimo.
1
2
3
4
5
6
7
8
9
10
11
internal void *bitmap_memory;
internal int bitmap_width;
internal int bitmap_height;
internal int bytes_per_pixel = 4;
int bitmap_memory_size = bitmap_width * bitmap_height * bytes_per_pixel;
bitmap_memory = VirtualAlloc(0, // start address (optional)
bitmap_memory_size,
MEM_RESERVE|MEM_COMMIT, // comitar e já começar a usar esse memória
PAGE_READWRITE); // permissão
Para livrar espaço, vamos usar o VirtualFree
.
1
2
3
4
5
6
7
8
9
10
11
if (bitmap_memory) {
VirtualFree(&bitmap_memory, 0, MEM_RELEASE);
}
// o tamanho do bitmap será o tamanho da nova janela vinda do WM_SIZE.
bitmap_width = width;
bitmap_height = height;
bitmap_info.bmiHeader.biSize = sizeof(bitmap_info.bmiHeader); // size of bitmap header
bitmap_info.bmiHeader.biWidth = bitmap_width;
bitmap_info.bmiHeader.biHeight = -bitmap_height; // negative - top-down left corner when rendering
Já no update_window()
, vamos escalar o bitmap para ficar de acordo com a janela.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
internal void
update_window(HDC context, RECT *client_rect, int x, int y, int width, int height)
{
int window_width = client_rect->right - client_rect->left;
int window_height = client_rect->bottom - client_rect->top;
StretchDIBits(context,
0, 0, window_width, window_height, // dest
0, 0, bitmap_width, bitmap_height, // src
bitmap_memory,
&bitmap_info,
DIB_RGB_COLORS,
SRCCOPY);
}
Criando Cores de Teste
Vamos preencher o bitmap com algumas cores. Para isso precisaremos do loop entre width
x height
x bytes_per_pixel
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int pitch = width * bytes_per_pixel;
uint8 *row = (uint8 *) bitmap_memory; // usamos uint8 aqui pois no incremento com o pitch conseguimos seguir para próxima linha
for (int y = 0; y < height; ++y) {
uint8 *pixel = (uint8 *) row; // acessando um canal por vez BB GG RR xx
for (int x = 0; x < width; ++x) {
*pixel = 255; // blue
++pixel;
*pixel = 0;
++pixel;
*pixel = 0;
++pixel;
*pixel = 0;
++pixel;
}
row += pitch;
}
Sempre que incrementamos um ponteiro, ele irá avançar o tamanho em bytes. Por exemplo,
pixel++
avançará 1 byte porque pixel é umuint8
.
A saída do código acima preencherá a tela com uma cor azul porque neste caso o Windows usa arquitetura Little Endian - BB GG RR xx.
Corrigindo o Main Loop
GetMessage
é problemático. Ele sempre irá esperar para outra mensagem chegar na fila e usa o tempo da CPU (mas queremos usar o tempo da CPU só pra nós).
PeekMessage
fará a mesma coisa que o GetMessage que é receber mensagens, mas quando nao houver mensagens ele irá manter a execução ao invés de bloquear a thread.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
while(running) {
Message message;
// BOOL messageResult = GetMessage(&message, 0, 0, 0);
// processa até a fila não ter mais mensagens.
// Ou seja, vai consumindo os inputs como clicks, keyboard, exit close, etc
// PM_REMOVE remove a mensagem da fila.
while (PeekMessage(&message, 0, 0, 0, PM_REMOVE)) {
if (message.message == WM_QUIT) {
running = false;
}
TranslateMessage(&message);
DispatchMessage(&message); // Dispatches a message to a window procedure
}
}
Criando um Gradiente Estranho
Podemos agora alterar os valores de blue e green baseado nas coordenadas X e Y.
1
2
3
4
5
*pixel = (uint8) y;
++pixel;
*pixel = (uint8) x;
++pixel;
Como funciona cores
Como cada canal do pixel corresponde à 256 bits (1 byte), o resultado do desenho na tela será uma cor gradiente de 256 largura e 256 de height, resetando para zero sempre que o número de Y ou X ultrapassar 255 (overflow).
0 1 2 3 …. 255 0 1 2 3 …. 255
Vamos mover esse bloco para uma função com parâmetros para movimentar o eixo X e Y.
Nota: Desta vez, não vamos inserir 1 byte por vez. Agora vamos inserir diretamente 4 bytes com a ajuda dos operadores OR e SHIFT-LEFT
<<
que irá colocar cada canal de uma vez só formando 24bits.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
internal void render_gradient(int x_offset, int y_offset)
{
int pitch = bitmap_width * bytes_per_pixel;
uint8 *row = (uint8 *) bitmap_memory;
for(int y = 0; y < bitmap_height; ++y) {
// uma escrita com 32bits é mais rápida que 8bits
uint32 *pixel = (uint32 *) row;
for (int x = 0; x < bitmap_width; ++x) {
uint8 b = (x + x_offset);
uint8 g = (y + y_offset);
*pixel = ((g << 8) | b);
*pixel++;
}
row += pitch;
}
}
Vamos a outro exemplo de atribuição de cores.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
*pixel = x; // B
++pixel;
*pixel = y; // G
++pixel;
*pixel = 0; // R
++pixel;
*pixel = 0; // x
++pixel;
// Para que possamos representar em 32 bits,
// vamos definir primeiro os bits mais baixos (x) e depois,
// usar o operador OU desses bits com os próximos 8 bits acima.
*pixel = ((y << 8) | x);
// Neste exemplo estou fazendo:
// [ OU b ]
// [ g OU ] ( aqui subi 8 bits com << )
Animando a Tela Com Pixels
Com o nosso main loop não-bloqueante, vamos animar os pixels fazendo com que o eixo X incremente a cada frame e seja atualizado pelo StretchDIBits
.
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
int x = 0;
int y = 0;
while(running) {
MSG message;
while (PeekMessage(&message, 0, 0, 0, PM_REMOVE)) { // Fica renderizando e tentando buscar mensagens para dispachar.
if (message.message == WM_QUIT)
running = false;
TranslateMessage(&message);
DispatchMessage(&message);
}
// get window size
RECT client_rect;
GetClientRect(window, &client_rect);
int window_width = client_rect.right - client_rect.left;
int window_height = client_rect.bottom - client_rect.top;
HDC context = GetDC(window);
// atualiza o jogo (bitmap)
render_gradient(x, y);
// aplica as mudanças
update_window(context, &client_rect, 0, 0, window_width, window_height);
ReleaseDC(window, context);
x++;
}