Post

Primeira janela no Windows

Nas próximas linhas irei mostrar como iniciar uma janela com um looping para iniciar a renderização de pixels na tela.

Vamos começar renderizando com a CPU para depois usar a GPU no futuro.

Os passos para chegar no resultado de uma janela para o jogo serão:

  1. Definir uma struct WNDCLASS com as definições
  2. Definir um ponteiro (WindowProc) nessa Window que será nossa função de callback com os eventos de teclado e eventos do Windows para a janela
  3. Registrar a WNDCLASS para poder criar uma Window de fato com o RegisterClass
  4. Criar a janela através das instrução CreateWindow
  5. Criar um main-loop para manter a janela ativa e dispachar os eventos que o Windows fornece para o procedure (GetMessage + WindowProc)

Criando a WindowClass

Algumas propriedades que vamos usar:

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct tagWNDCLASSA {
  UINT      style;
  WNDPROC   lpfnWndProc;
  // int       cbClsExtra;
  // int       cbWndExtra;
  HINSTANCE hInstance;
  // HICON     hIcon;
  // HCURSOR   hCursor;
  // HBRUSH    hbrBackground;
  // LPCSTR    lpszMenuName;
  LPCSTR    lpszClassName;
} WNDCLASSA, *PWNDCLASSA, *NPWNDCLASSA, *LPWNDCLASSA;
  • style: binary information
  • lpfnwndproc: ponteiro para nossa função de callback que irá escutar eventos da janela (resize, create, etc)
  • cbClsExtra e cbWndExtra: extras bytes alocados na sequencia da struct
  • hInstance: handle instance que terá essa window
  • hIcon: icone
  • hCursor: cursor
  • hBrbackground: Cor de fundo
  • lpszMenuName: Menus
  • lpszClassName: O nome (String) da class

NOTA: Quando não tivermos o HWND e precisarmos dele, podemos buscar a hInstance com a seguinte função em qualquer lugar do nosso executável. GetModuleHandle(0);

Window Class Flags

  • CS\_VREDRAW: Redesenha toda a janela se o movimento ou ajuste do tamanho mudar na vertical.
  • CS\_HREDRAW: Redesenha toda a janela se o movimento ou ajuste do tamanho mudar na horizontal.
  • CS\_OWNDC: Aloca somente um único DeviceContext para cada janela.

Callback de eventos

Para capturar mensagens da janela, temos uma procedure para isso.

1
2
3
4
5
6
LRESULT Wndproc(
  HWND window,
  UINT message, // The message
  WPARAM w_param, // Additional message information
  LPARAM l_param // Additional message information
)

Exemplo:

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
LRESULT CALLBACK main_window_callback(HWND hwnd, UINT message, 
                                      WPARAM w_param, LPARAM l_param) 
{
  LRESULT result = 0;
  switch(message) {
    case WM_CREATE: {
      // podemos pegar o valor do lParam passado ao criar uma window
      OutputDebugStringA("WM_CREATE\n");
    } break;
    case WM_SIZE: {
      OutputDebugStringA("WM_SIZE\n");
    } break;
    case WM_DESTROY: {
      OutputDebugStringA("WM_DESTROY\n");
    } break;
    case WM_CLOSE: {
      OutputDebugStringA("WM_CLOSE\n");
    } break;
    case WM_PAINT: {
	  // ...
    } break;
    case WM_ACTIVATEAPP: {
      OutputDebugStringA("WM_ACTIVATEAPP\n");
    } break;
    default: {
      result = DefWindowProc(hwnd, message, wParam, lParam);
    } break;
  }
  return result;
}

LRESULT possui 8 bytes

Criando a Janela

É preciso registrar a WNDCLASS para usá-la. RegisterClass(&window_class). Caso a função retorne algo diferente de NULL, conseguimos criar a janela com o CreateWindow.

It specifies the window class, window title, window style, and (optionally) the initial position and size of the window

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
WNDCLASS windowClass      = {};
windowClass.style         = CS_OWNDC | CCS_HREDRAW | CS_VREDRAW;
windowClass.lpfnWndProc   = main_window_callback; // <- nosso callback
windowClass.hInstance     = hInstance;
windowClass.lpszClassName = "MyWindowClass";

if (!RegisterClass(&windowClass)) {
    // TODO: Log
    return -1;
}
HWND window_handle = CreateWindowEx(
    NULL, // DWORD dwExStyle valores extras 
	// exemplo Forces a top-level window onto the 
	// taskbar when the window is visible.
	windowClass.lpszClassName, // LPCTSTR lpClassName
	"MyGame", // LPCTSTR lpWindowName
	WS_OVERLAPPEDWINDOW | WS_VISIBLE, // DWORD dwStyle
	CW_USEDEFAULT, // int x
	CW_USEDEFAULT, // int y
	CW_USEDEFAULT, // int nWidth 
	CW_USEDEFAULT, // int nHeight
	NULL, // HWND hWndParent
	NULL, // HMENU hMenu
	hInstance, // HINSTANCE hInstance
	NULL // LPVOID lpParam
); 

if (!window_handle) {
    // TODO: log system
    return -1;
}

// more code...

return 0;

O último parâmetro da CreateWindow é um lpvoid onde podemos passar informações que chegarão no LParam pela message WM_CREATE na procedure de callback da window.

Main Loop

Precisamos extrair as mensagens que o Windows manda para nossa janela. Isto é, os eventos que o Windows dispara ou os inputs de teclado precisam ser extraídos de dentro do loop.

As mensagens ficam em uma fila e precisamos retirá-la com o GetMessage.

NOTE: GetMessage irá travar a fila de mensagens. Futuramente vamos trocar para PeekMessage.

Depois de pegar cada uma das mensagens, vamos traduzi-las (Translates virtual-key messages into character messages) e dispachá-las para a procedure definida anteriormente no WNDCLASS com o DispatchMessage.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if (!window_handle) {
    // TODO: log system
    return -1;
}

MSG message; // windows vai colocar a mensagem aqui atraves do ponteiro

for(;;) {
    // LPMSG lpMsg
    // HWND hWnd (if NULL, busca mensagens de qualquer Window da thread atual)
    // UINT wMsgFilterMin (filtro de mensagens disponiveis - range | zero = all messages)
    // UINT wMsgFilterMax (filtro de mensagens disponiveis - range | zero = all messages)
    BOOL result = GetMessage(&message, 0, 0, 0);

    if (result > 0) {
	    TranslateMessage(&message);
	    DispatchMessage(&message); // Dispatches a message to a window procedure
    } else {
	    break;
    }

}

Colorindo a Janela

O Seguinte código é da window procedure onde podemos usar as instruções de BeginPaint e EndPaint para colorir a região da janela e usar a struct PAINTSTRUCT para isso.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
case WM_PAINT: {
  PAINTSTRUCT paint;
  // busca as definicoes da hwnd para poder pintar nas dimensoes (x, y, w, h)
  HDC deviceContext = BeginPaint(hwnd, &paint);

  int x = paint.rcPaint.left;
  int y = paint.rcPaint.top;
  int height = paint.rcPaint.bottom - paint.rcPaint.top;
  int width  = paint.rcPaint.right  - paint.rcPaint.left;

  static DWORD operation = WHITENESS;

  PatBlt(deviceContext, x, y, width, height, operation);

  operation = operation == WHITENESS ? BLACKNESS : WHITENESS;

  EndPaint(hwnd, &paint);
} break;

Notas Importantes

Static em variáveis locais

Quando definimos um static dentro de uma variável local, ele irá persistir como se fosse global. Contudo, sua visibilidade é somente aquele escopo de função.

Use somente para debug!!

1
static DWORD operation = WHITENESS;

Diferenças de Structs em C vs C++

Iniciando uma struct:

1
2
struct Foo foo; // C
Foo foo; // C++

Declarando uma struct:

1
2
3
4
5
6
7
8
// C
typedef struct {
  // ...
} Name;

// C++
struct Name {
};

Inicializando uma struct:

Podemos iniciar uma struct com todos valores zerados com o bloco {}.

1
WNDClass window_class = {};

Referências

Esta postagem está licenciada sob CC BY 4.0 pelo autor.