Tutorial SDL – Parte 1 – Hello SDL

Neste primeiro tutorial sobre SDL publicado originalmente no site lazyfoo, iremos configurar a biblioteca e criar nossa primeira janela.

SDL é um biblioteca de ligação dinâmica; dessa forma ela possui três partes:

  • Os arquivos de cabeçalho (*.h)
  • Os arquivos da biblioteca (*.lib no Windows ou lib*.a nos *nix)
  • Os arquivos binários (*.dll no Windows ou *.so nos *nix)

Seu compilador precisa ser capaz de encontrar os arquivos de cabeçalho ao compilar seu código para saber do que se trata SDL_Init() e outras funções e outras estruturas do SDL. Você pode tanto configurar seu compilador para procurar em um diretório adicional por esses arquivos, ou por esses arquivos no mesmo local dos outros arquivos de cabeçalho que seu compilador usa. Se o seu compilador reclamar que não consegue encontrar SDL.h, isso significa que os arquivos de cabeçalhos não estão em um local que o compilador possa acha-los.
Depois que o seu compilador compila todos o seu código, precisa liga-los todos juntos. Para que isso seja feito de forma adequada, precisam ser conhecidos os endereços de todas as funções, incluindo as do SDL. Para uma biblioteca de ligação dinâmica, esses endereços estão em um arquivo de biblioteca. Esse arquivo possui uma Import Address Table (Tabela de endereços para importação) de forma que seu programa possa importar essas funções durante a execução. Da mesma forma que com os arquivos de cabeçalho, você pode tanto configurar seu compilador para procurar as bibliotecas em um diretório específico ou por os arquivos junto com o restante dos arquivos de biblioteca que acompanham seu compilador. Você também precisa informar ao linker para fazer a ligação com o arquivo de biblioteca durante esse processo. Se o linker reclamar que não pode encontrar -lSDL ou SDL2.lib, isso significa que os arquivos não estão em um local que possam ser encontrados. Se o linker reclamar de uma referência não definida (undefined reference), isso significa que você não informou a ele para usar a ligação com a biblioteca.
Depois que seu programa é compilado e as ligações com as bibliotecas foram definidas, você precisa ser capaz de efetuar a ligação com as bibliotecas quando executa a aplicação. Para poder rodar um aplicação com ligações dinâmicas, você precisa ser capaz de importar os arquivos binários da biblioteca durante a execução. Seu sistema operacional precisa ser capaz de encontrar os arquivos binários ao executar seu programa. Você tanto pode por esses arquivos no mesmo diretório de seu executável, quando por eles em um diretório utilizado pelo sistema operacional para armazenar esses arquivos.

Windows

Uma observação importante para desenvolvedores Windows:
Como parte do processo de configuração, você irá ter que colocar os arquivos DLL em algum local que esteja acessível ao seu programa durante a execução. Você pode tanto por o arquivo no mesmo diretório de seu executável, quanto por o arquivo no diretório C:\WINDOWS\SYSTEM32 para sistemas de 32 bits ou no diretório C:\WINDOWS\SysWOW64 para sistemas 64 bits de aplicações 32 bits.
As vantagens de por o arquivo DLL no diretório do sistema são:

  1. Seu sistema operacional sempre será capaz de encontrar a biblioteca em seu sistema, de forma que você poderá compilar e executar aplicações com ligações dinâmicas em qualquer lugar
  2. Você não precisará incluir uma cópia do arquivo DLL com cada aplicação que você desenvolver

Tendo isso em mente, ao distribuir sua aplicação você nunca deve mexer com o diretório SYSTEM do usuário. Ele poderia ter aplicações que precisem da versão ABC do SDL em seu diretório SYSTEM e você sobrescrever ela com a versão XYZ. Sua distribuição deve por as arquivos DLL no mesmo diretório de seu executável.

1) A primeira coisa que precisa ser feita é baixar os arquivos de cabeçalho, biblioteca e binários do SDL. Você pode encontra-los no website do SDL, especificamente nesta página.
Você irá baixar as bibliotecas de desenvolvimento para o MinGW.
mingw_package

Abra o arquivo gzip e dentro dele você verá um arquivo tar. Abra o arquivo tar e você verá um diretório com o nome SDL2-2.something.something. Dentro desse diretporio deve haver um punhado de arquivos e diretórios, sendo os mais importantes i686-w64-mingw32 que contém a versão 32 bits da biblioteca e x86_64-w64-mingw32 que contém a versão 64 bits da biblioteca.
Isso é importante: A maioria dos compiladores ainda gera binários de 32 bits para maximizar a compatibilidade. Usaremos os binários de 32 bits nesse tutorial. Não importa se você possuir um sistema operacional de 64 bits, já que criaremos arquivos binários de 32 bits iremos usar a biblioteca de 32 bits.
Dentro desse diretório estarão diretórios includelibbin que contém tudo que você precisa para compilar e executar aplicações SDL. Copie cada um desses diretórios para o local que você quiser. Eu recomendo por esses diretórios no local onde você instalou as bibliotecas de desenvolvimento do MinGW.
2) Em seguida iremos configurar o caminho para o MinGW de forma que possamos executar comandos dele em qualquer diretório. Abra o menu sistema seja por A) clique com o botão direito em Meu Computador e selecionando Propriedades ou B) indo ao Painel de controle e selecionando o menu sistema. Quando tiver feito isso, clique no link Configurações avançadas do sistema.

system

e então clique em Variáveis de ambiente:
environment_variables

Sob variáveis de ambiente, selecione a variável Path e clique em editar:

edit_path

O que a variável Path faz é informar ao sSO onde procurar quando precisar executar um executável. O que queremos fazer é onde quer que executemos o comando g++, o SO deve olhar no diretório bin do MinGW onde g++.exe está localizado. Se você próprio instalou o MinGW, o diretório bin deve estar localizado em C:\MinGW\bin.

Anexe este diretório à lista de caminhos com um ponto-e-virgula após ele e clique em OK.
add_path

Agora quando você executar um comando que use um executável do MinGW, o SO irá saber que deve procura-lo no diretório bin do MinGW.
Se você executar seu programa e ele reclamar que não pode encontrar SDL2.dll, significa que não configurou o Path corretamente
3) Agora baixe o código fonte da lição 01. Descompacte o código em algum lugar, Abra uma janela de terminal nesse diretório clicando com o botão direito enquanto segura SHIFT.

command

Agora compile o código digitando esse comando:

g++ 01_hello_SDL.cpp -IC:\mingw_dev_lib\include\SDL2 -LC:\mingw_dev_lib\lib -w -Wl,-subsystem,windows -lmingw32 -lSDL2main -lSDL2 -o 01_hello_SDL

Ter que digitar manualmente esse comando toda vez que precisar compilar fica rapidamente muito chato; por isso é recomendável usar Make.
4) O Make (já incluído com o MingGW) permite que você crie scripts de compilação que automatizam o processo.

Basic Makefile
#OBJS specifies which files to compile as part of the project
OBJS = 01_hello_SDL.cpp
#OBJ_NAME specifies the name of our exectuable
OBJ_NAME = 01_hello_SDL
#This is the target that compiles our executable
all : $(OBJS)
   g++ $(OBJS) -IC:\mingw_dev_lib\include\SDL2 -LC:\mingw_dev_lib\lib -w -Wl,-subsystem,windows -lmingw32 -lSDL2main -lSDL2 -o $(OBJ_NAME)
Aqui temos uma Makefile básico. No topo declaramos uma macro OBJS que especifica que arquivos iremos compilar. Em seguida declaramos uma macro OBJ_NAME que especifica o nome do arquivo executável.
Depois de declarar essas duas macros, temos o alvo “all” que compila o programa. É seguido pelas dependências que você pode ver em sua macro OBJS, porque obviamente você precisa do código fonte para compilar o programa.
Depois de especificar o nome do alvo e suas dependências, o comando para criar o alvo vem na linha seguinte. O comando para criar o alvo precisa ser iniciado com um TAB ou o Make irá rejeita-lo.
Como esperado, o comando para compilar o programa é basicamente o mesmo do comando utilizado para fazer isso manualmente. Uma diferença importante é que temos macros que inserimos no comando que tornam a adição de novos arquivos ao projeto muito mais fácil, já que agora você precisa apenas alterar a macro ao invés de alterar todo o comando.
Nos próximos artigos, estaremos usando mais bibliotecas. Assim, devemos provavelmente usar mais macros para que o processo de adicionar esse bibliotecas facilitado.
Makefile
#OBJS specifies which files to compile as part of the project
OBJS = 01_hello_SDL.cpp
#CC specifies which compiler we're using
CC = g++
#INCLUDE_PATHS specifies the additional include paths we'll need
INCLUDE_PATHS = -IC:\mingw_dev_lib\include\SDL2
#LIBRARY_PATHS specifies the additional library paths we'll need
LIBRARY_PATHS = -LC:\mingw_dev_lib\lib
#COMPILER_FLAGS specifies the additional compilation options we're using
# -w suppresses all warnings
# -Wl,-subsystem,windows gets rid of the console window
COMPILER_FLAGS = -w -Wl,-subsystem,windows
#LINKER_FLAGS specifies the libraries we're linking against
LINKER_FLAGS = -lmingw32 -lSDL2main -lSDL2
#OBJ_NAME specifies the name of our exectuable
OBJ_NAME = 01_hello_SDL
#This is the target that compiles our executable
all : $(OBJS)
 $(CC) $(OBJS) $(INCLUDE_PATHS) $(LIBRARY_PATHS) $(COMPILER_FLAGS) $(LINKER_FLAGS) -o $(OBJ_NAME)
Agora nosso comando de compilação ficou muito mais flexível.
Próximo ao topo, temos as macros que definem os arquivos que iremos compilar e o compilador que usaremos.
Em seguida temos a macro INCLUDE_PATHS que especifica os diretórios adicionais onde os arquivos de cabeçalho estão localizados. Como você pode ver, estamos usando o diretório include do diretório SDL2 que extraimos anteriormente. A macro LIBRARY_PATHS define o caminho para os arquivos de biblioteca. Observe como temos um -I antes de cada diretório include e um -L antes de cadas diretório de biblioteca.
A macro COMPILER_FLAGS são as opções adicionais que usaremos ao compilar. Nesse caso, estamos desativando todos os aviso e desativando a janela do terminal. a macro LINKER_FLAGS especifica quais bibliotecas iremos utilizar. Neste exemplo iremos usar a versão de 32 bits de mingw, SDL2 e SDL2main. Observe que existe um -l antes de cada biblioteca.
Finalmente, na parte inferior temos nosso alvo de compilação usando todas essas macros. Graças a essas macros, podemos facilmente adicionar mais arquivos e bibliotecas quando for preciso.
Save esse código Makefile em um arquivo com o nome Makefile (exatamente dessa forma). Abra uma janela de terminal no diretório do código fonte e execute o comando mingw32-make.exe. O Make irá procurar por um arquivo Makefile no diretório em que foi executado e executará os comandos que compilarão o código.

Linux

Na era dos gerenciadores de pacotes, um bocado de trabalho é feito para você ao instalar bibliotecas. Você precisará de privilégios de administrador para instalar o pacotes então certifique-se de usar “su” ou “sudo” se não estiver logado como root.
Certifique que você tenha atualizado para a versão mais recente de sua distribuição pois seu gerenciador de pacote antigo pode não suportar a versão mais recente do SDL.
1) Para aqueles que possuem o Advanced Packaging Tool (como Ubuntu e Debian) você terá que pesquisar com o comando apt-get cache para encontrar a versão do SDL 2 a ser instalada. Você pode pesquisar pelos pacotes disponíveis com o comando:

apt-cache search libsdl2

Você irá precisar baixar a versão de desenvolvimento do SDL 2. Quando este artigo foi escrito, a versão de desenvolvimento do SDL 2 é a libsdl2-dev. Você pode baixar esse pacote usando o comando:

apt-get install libsdl2-dev

2) Se você estiver usando o Yellow dog Updater Modified (usado no Fedora e CentOS) pode usar o seguinte comando:

yum search SDL2-devel

Para procurar pelo pacote de desenvolvimento do SDL 2. Quando esse artigo foi escrito, esse pacote era o SDL2-devel. Você pode instalar esse pacote com o comando:

yum install SDL2-devel

3) Se de alguma forma você não possuir um gerenciador de pacote, pode instalar compilando o código fontes da forma clássica do Unix. Baixe a última versão do código fontes no website do SDL.

mingw_package
Extraia o arquivo e entre no diretório que você extraiu. Configure a instalação usando o comando:

./configure

Compile o código fonte usando o seguinte comando do make:

make all

Finalmente, instale o pacote usando o seguinte comando do make:

make install

Nota: se você fizer uma instalação manual, você pode ter que especificar onde os arquivos de cabeçalho e de biblioteca estão para seu compilador/IDE.
4) Agora quer você instalou os arquivos da biblioteca, é hora de abrir seu IDE/Compilador favorito.

MacOSX

1) A primeira coisa que precisa ser feito é baixar os arquivos de desenvolvimento para o OS X no website do SDL.
mingw_package

2) Em seguida abra o arquivo DMG e copie o SDL2.framework para o diretório /Library/Frameworks. Para ir diretamente ao caminho no Finder, pressione command+shift+g.
copy_framework

3) O framework pode precisar ser reassinado. Para fazer isso, abra uma janela de terminal no diretório /Library/Frameworks/sdl2.framework/ e assine o framework com o comando:

codesign -f -s – SDL2

4) Agora quer você instalou os arquivos da biblioteca, é hora de abrir seu IDE/Compilador favorito.

Sua primeira janela

Para finalizar, iremos cobrir agora a primeira grande etapa: obter uma janela.
Agora que você configurou o SDL, chegou a hora de criar uma aplicação SDL básica que irá renderizar uma janela na tela.
//Using SDL and standard IO
#include <SDL.h>
#include <stdio.h>
//Screen dimension constants
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
Na parte superior do código, iremos incluir os arquivos de cabeçalho do SDL pois precisaremos de algumas funções e tipos de dados do SDL em nosso código. Iremos também incluir arquivos de cabeçalho padrão do C para imprimir error no terminal. Você pode estar mais acostumado a usar o iostream, mas aqui usaremos printf por ser mais seguro. Nessas aplicações iniciais, use o que for mais confortável para você.
Depois de incluir o arquivos de cabeçalho, declaramos a largura e altura da janela que iremos renderizar.
int main( int argc, char* args[] )
{
 //The window we'll be rendering to
 SDL_Window* window = NULL;
 //The surface contained by the window
 SDL_Surface* screenSurface = NULL;
 //Initialize SDL
 if( SDL_Init( SDL_INIT_VIDEO ) < 0 )
 {
 printf( "SDL could not initialize! SDL_Error: %s\n", SDL_GetError() );
 }
Aqui está a parte superior de nossa função main. É importante que tenhamos como argumentos dessa função um inteiro seguido de uma matriz de caracteres e um tipo de retorno inteiro. Qualquer outro tipo de função main irá causar um erro undefined reference to main. O SDL requer esse tipo de função main por ser compatível com múltiplas plataformas.
Em seguida, declaramos nossa janela SDL que estaremos criando em instantes. Após isso, temos uma superfície SDL da tela. Uma superfície SDL é apenas uma imagem 2D. Uma imagem 2D pode ser carregada de um arquivo ou pode ser a imagem interna da janela. No nosso caso, será a imagem que veremos dentro da janela na tela.
Depois da declaração de nossa janela e superfície da tela, iremos inicializar o SDL. Você não pode chamar nenhuma função SDL sem ter inicializado o SDL. Como só queremos usar o subsistema de vídeo do SDL, iremos informar apenas a flag SDL_INIT_VIDEO.
Quando um erro acontece, SDL_Init retorna -1. Quando  isso acontece, queremos imprimir no terminal o que aconteceu, senão a aplicação apenas irá aparecer an tela por 1 segundo e irá ser fechada.
Se você nunca usou o printf antes, ele significa formato de impressão. Ele mostra na tela a string do primeiro argumento com as variáveis dos argumentos seguintes. Quando acontece um erro, é impresso na tela “SDL could not initialize! SDL_Error:” será escrito no terminal seguido pela string retornada por SDL_GetError. O %s é um formato especial que significa que a saída de nossa variavel é uma string. Como SDL_GetError é o único argumento, a string retornada será adicionada. SDL_GetError retorna o último erro produzido por uma função SDL.
SDL_GetError é uma função muito útil. Quando alguma coisa de errado acontece você precisa saber o porquê. SDL_GetError permite que você tome conhecimento de qualquer error que aconteça dentro de uma função SDL.
 else
 {
 //Create window
 window = SDL_CreateWindow( "SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
 if( window == NULL ) {
  printf( "Window could not be created! SDL_Error: %s\n", SDL_GetError() );
 }
Se o SDL for inicializado com sucesso, queremos agora criar a janela usando a função SDL_CreateWindow. O primeiro argumento informa o titulo da janela ou dessa parte da janela:
caption
Os dois argumentos seguintes definem a posição x e y onde a janela será criada. Como no nosso exemplo não é importante onde ela é criada, apenas usamos SDL_WINDOWPOS_UNDEFINED para esses dois valores. Em seguida, indicamos a largura e altura a janela. O último argumento são flags de criação. SDL_WINDOW_SHOWN certifica que a janela seja exibida quando é criada. Se ocorrer um erro, SDL_CreateWindow retorna NULL. Se não existe nenhuma janela a ser exibida, é desejável exibir o erro no terminal.
 else
 {
 //Get window surface
 screenSurface = SDL_GetWindowSurface( window );
 //Fill the surface white
 SDL_FillRect( screenSurface, NULL, SDL_MapRGB( screenSurface->format, 0xFF, 0xFF, 0xFF ) );
 //Update the surface
 SDL_UpdateWindowSurface( window );
 //Wait two seconds
 SDL_Delay( 2000 );
 }
}
Se a janela foi criada com sucesso, precisamos agora obter a superfície da janela de forma que ela seja possa desenhada. SDL_GetWindowSurface faz apenas isso.
Para efeito de simplificação, tudo que iremos fazer aqui é preencher a superfície da janela com a cor branca usando SDL_FillRect. Não se preocupe com essa função aqui. Nesse artigo nossa única preocupação é obter a janela.
Uma coisa importante sobre a renderização é que apenas desenhar algo na tela não significa que você verá essa coisa. Depois que você tiver feito todos os desenhos precisa atualizar a janela de forma que o que você desenhoi seja exibido. Uma chamada para SDL_UpdateWindowSurface faz isso.
Se tudo que fizermos for criar a janela, preenche-la e atualiza-la, tudo que iremos ver é a janela aparecer por 1 segundo e então ser fechada. Para evitar que ela desapareça, iremos chamar SDL_Delay. Essa função irá aguardar por uma dada quantidade de milisegundos. Um milisegundo é 1/1000 do segundo. Isso significa que no código acima a janela irá ser exibida por 2000 1/1000 de 1 segundo, ou seja 2 segundos.
Uma detalhe importante aqui é que quando o SDL estiver esperando, ele não aceita nenhuma entrada do teclado ou mouse. Não entre em pânico quando você executar esse programa e ele não responder. Nós ainda não temos nenhum código para lidar com o mouse e teclado.
//Destroy window SDL_DestroyWindow( window ); //Quit SDL subsystems SDL_Quit(); return 0; }
Depois que os 2 segundos de espera passam, destruimos a janela para liberar memória. Isso também lida com o SDL_Surface que obtemos nesse processo. Depois que tudo foi desalocado, saimos de SDL e retornamos 0 para encerrar o programa.