1.5 ABCg

Para facilitar o desenvolvimento das atividades práticas utilizaremos a biblioteca ABCg desenvolvida especialmente para esta disciplina.

A ABCg permite a prototipagem rápida de aplicações gráficas interativas 3D em C++ capazes de rodar tanto no desktop (binário nativo) quanto no navegador (binário WebAssembly).

Internamente a ABCg utiliza a biblioteca SDL para gerenciar o acesso a dispositivos de entrada (mouse/teclado/gamepad) e saída (vídeo e áudio) de forma independente de plataforma, e a biblioteca GLEW para acesso às funções da API gráfica OpenGL. Além disso, a API do Emscripten é utilizada sempre que a aplicação é compilada para gerar binário WebAssembly.

A ABCg é mais propriamente um framework do que uma biblioteca de funções, pois assume o controle da aplicação. Por outro lado, a camada de abstração para as APIs utilizadas é mínima e é possível acessar as funções da SDL e OpenGL diretamente (e faremos isso sempre que possível). Outras bibliotecas também utilizadas e que podem ser acessadas diretamente são:

  • CPPIterTools: para o suporte a laços range-based em C++ usando funções do tipo range, enumerate e zip similares às do Python;
  • Dear ImGui: para gerenciamento de widgets de interface gráfica do usuário, tais como janelas, botões e caixas de edição;
  • {fmt}: como alternativa mais eficiente ao stdio da linguagem C (printf, scanf, etc) e iostreams do C++ (std::cout, std::cin, etc), e para formatação de strings com uma sintaxe similar ao str-format do Python;
  • Guidelines Support Library (GSL): para uso de funções e tipos de dados recomendados pelo C++ Core Guidelines;
  • OpenGL Mathematics (GLM): para suporte a operações de transformação geométrica com vetores e matrizes;
  • tinyobjloader: para a leitura de modelos 3D no formato Wavefront OBJ.

A seguir veremos como instalar e compilar a ABCg junto com um exemplo de uso.

Instalação

Em um terminal, clone o repositório do GitHub:

git clone https://github.com/hbatagelo/abcg.git
Observação

A versão mais recente da ABCg (atualmente v2.0.0) também pode ser baixada como um arquivo compactado de https://github.com/hbatagelo/abcg/releases/latest.

Atenção

No Windows, certifique-se de clonar o repositório em um diretório cujo nome não contenha espaços ou caracteres especiais. Por exemplo, clone em C:\cg em vez de C:\computação gráfica.

O repositório tem a estrutura mostrada a seguir. Para simplificar, os arquivos e subdiretórios .git* foram omitidos:

abcg
│   .clang-format
│   .clang-tidy
│   build.bat
│   build.sh
│   build-wasm.bat
│   build-wasm.sh
│   CMakeLists.txt
│   LICENSE
│   README.md
│   runweb.bat
│   runweb.sh
│   VERSION.md
│
└───abcg
│   │   ...
│
└───cmake
│   │   ...
│   
└───examples
│   │   ...
│
└───public
    │   ...

Os arquivos .clang-format e .clang-tidy são arquivos de configuração utilizados pelas ferramentas ClangFormat (formatação) e Clang-Tidy (linter) caso estejam instaladas.

Os arquivos .sh são shell scripts de compilação e execução em linha de comando. Note que há scripts correspondentes com extensão .bat para usar no Prompt de Comando do Windows (o PowerShell não é suportado):

  • build.sh: para compilar a biblioteca e os exemplos em binários nativos;
  • build-wasm.sh: similar ao build.sh, mas para gerar binário em WebAssembly dentro do subdiretório public;
  • runweb.sh: para rodar um servidor web local que serve o conteúdo de public.

O arquivo CMakeLists.txt é o script de configuração utilizado internamente pelo CMake.

Os subdiretórios são os seguintes:

  • abcg contém o código-fonte da biblioteca e suas dependências;
  • cmake contém scripts auxiliares de configuração do CMake;
  • examples contém um exemplo de uso da ABCg: um “Hello, World!” que usa OpenGL e interface da ImGui;
  • public contém páginas web para exibir o exemplo “Hello, World!” no navegador.

Compilando em linha de comando

Execute o script build.sh (Linux/macOS) ou build.bat (Windows) para iniciar o processo de configuração e construção. A saída será similar a esta (o exemplo a seguir é do Windows):

-- The C compiler identification is GNU 10.3.0
-- The CXX compiler identification is GNU 10.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: C:/msys64/mingw64/bin/gcc.exe - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: C:/msys64/mingw64/bin/g++.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
Using ccache
-- Found OpenGL: opengl32
-- Found GLEW: C:/msys64/mingw64/lib/cmake/glew/glew-config.cmake
-- Looking for pthread.h
-- Looking for pthread.h - found
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success
-- Found Threads: TRUE
-- Found SDL2: mingw32;-mwindows;C:/msys64/mingw64/lib/libSDL2main.a;C:/msys64/mingw64/lib/libSDL2.dll.a
-- Found SDL2_image: C:/msys64/mingw64/lib/libSDL2_image.dll.a
-- Configuring done
-- Generating done
-- Build files have been written to: C:/abcg/build
...
[22/22] Linking CXX executable bin\helloworld.exe

Ao final, os binários estarão disponíveis no subdiretório build. A biblioteca estática estará em build/abcg/libabcg.a e o executável do exemplo “Hello, World!” estará em build/bin/helloworld.

Para testar, execute o helloworld. No Linux/macOS:

./build/bin/helloworld/helloworld

No Windows:

.\build\bin\helloworld\helloworld.exe | cat
Importante

No Windows, a saída deve sempre ser redirecionada para cat ou tee. Se isso não for feito, nenhuma saída de texto será exibida no terminal. Isso se deve a um bug do MSYS2.

Observação

Observe o conteúdo de build.sh (build.bat contém instruções equivalentes):

#!/bin/bash
set -euo pipefail

BUILD_TYPE=Debug

# Reset build directory
rm -rf build
mkdir -p build && cd build

# Configure
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..

# Build
if [[ "$OSTYPE" == "darwin"* ]]; then
  # macOS
  NUM_PROCESSORS=$(sysctl -n hw.ncpu)
else
  NUM_PROCESSORS=$(nproc)
fi
cmake --build . --config $BUILD_TYPE -- -j $NUM_PROCESSORS
  • A variável BUILD_TYPE está como Debug, mas pode ser modificada para Release, MinSizeRel ou RelWithDebInfo. Use a opção Debug (padrão) ou RelWithDebInfo enquanto estiver depurando o código. Use Release para gerar um binário otimizado e sem arquivos de símbolos de depuração (otimiza para gerar código mais rápido) ou MinSizeRel (otimiza para gerar binário de menor tamanho).

  • Observe que o script apaga o subdiretório build antes de criá-lo novamente. Portanto, não salve arquivos dentro de build pois eles serão apagados na próxima compilação!

  • A geração dos binários usando o CMake é composta de duas etapas: configuração (cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..) e construção (cmake --build . --config $BUILD_TYPE). A configuração gera os scripts do sistema de compilação nativo (por exemplo, arquivos Makefile ou Ninja). A construção dispara a compilação e ligação usando tais scripts. Todos os arquivos gerados na configuração e construção ficam armazenados no subdiretório build.

Compilando no Visual Studio Code

Primeiramente, clone o repositório abcg do GitHub como mostrado na seção anterior. Apague o subdiretório build caso você já tenha compilado via linha de comando.

No Visual Studio Code, selecione o menu “File > Open Folder…” e abra a pasta abcg. No canto inferior direito da janela aparecerá uma notificação solicitando se você quer configurar o projeto. Selecione “Yes.”

Ao fazer isso, será feita uma varredura no sistema para identificar os compiladores e toolchains visíveis no PATH. Uma lista de “kits” encontrados aparecerá na janela “Output” do CMake/Build, como a seguir:

[kit] Found Kit: Clang 11.0.0
[kit] Found Kit: GCC for x86_64-w64-mingw32 10.2.0

Ao final da varredura, selecione o kit “GCC for x86_64-w64-mingw32” versão 10 ou posterior. Ao fazer isso, será iniciado o processo de configuração do CMake. Esse processo gera os arquivos que serão utilizados pelo sistema de construção nativo dentro de um subdiretório build do projeto.

Se aparecer uma notificação pedindo para configurar o projeto sempre que ele for aberto, Responda “Yes”:

Após o término da configuração do CMake, aparecerá uma outra notificação solicitando permissão para configurar o Intellisense. Responda “Allow.”

Se, além disso, aparecer uma notificação sobre o arquivo compile_commands.json, como a seguir, responda “Yes” novamente:

compile_commands.json é um arquivo gerado automaticamente pelo CMake e que contém os comandos de compilação e o caminho de cada unidade de tradução utilizada no projeto. O IntelliSense utiliza as informações desse arquivo para habilitar as referências cruzadas.

Importante

A construção dos projetos usando o CMake é feita em duas etapas:

  1. Configuração: consiste na geração dos scripts do sistema de compilação nativo (por exemplo, arquivos Makefile ou Ninja);
  2. Construção: consiste no disparo da compilação e ligação usando os scripts gerados na configuração, além da execução de etapas de pré e pós-construção definidas nos scripts dos arquivos CMakeList.txt.

Tanto os arquivos da configuração quanto os da construção (binários) são gravados no subdiretório build.

Geralmente a configuração só precisa ser feita uma vez e depois refeita caso o subdiretório build tenha sido apagado, ou após a alteração do kit de compilação, ou após a alteração do build type (por exemplo, de Debug para Release).

Como indicado na figura abaixo, na barra de status há botões para selecionar o build type e configurar o CMake, selecionar o kit de compilação, e construir a aplicação. A opção de construir já se encarrega de configurar o CMake caso os arquivos de configuração ainda não tenham sido gerados.

Essas opções também estão disponíveis na paleta de comandos do editor, acessada com Ctrl+Shift+P. Os comandos são:

  • “CMake: Select Variant”: para selecionar um build type;
  • “CMake: Select a Kit”: para selecionar um kit de compilação;
  • “CMake: Configure”: para configurar o CMake usando o kit e o build type atual;
  • “CMake: Build”: para construir o projeto.
Observação

Os build types permitidos no CMake são:

  • Debug para gerar binários não otimizados e com arquivos de símbolos de depuração. Esse é o build type padrão;
  • RelWithDebInfo para gerar arquivos de símbolos de depuração com binários otimizados;
  • Release para gerar binários otimizados e favorecer código mais rápido. Essa opção não gera os arquivos de símbolos de depuração;
  • MinSizeRel, semelhante ao Release, mas a otimização tenta gerar binário de menor tamanho.

Para compilar e gerar os binários, tecle F7 ou clique em “Build” na barra de status. O progresso será exibido na janela “Output” do CMake/Build. Se a construção terminar com sucesso, a última linha de texto da janela Output será:

[build] Build finished with exit code 0

Os arquivos gerados na construção ficam armazenados no subdiretório build, da mesma forma como ocorre na compilação via linha de comando.

Para testar, abra um terminal e execute ./build/bin/helloworld/helloworld (Linux/macOS) ou .\build\bin\helloworld\helloworld.exe (Windows).

Atenção

A configuração do CMake gerada a partir do Visual Studio Code não é necessariamente a mesma gerada usando os scripts de linha de comando: o compilador pode ser diferente, ou o build type pode ser diferente.

Se em algum momento você construir o projeto via linha de comando usando os scripts .sh ou .bat e depois quiser construir pelo editor, certifique-se de apagar o subdiretório build antes de entrar no VS Code. Isso forçará uma nova configuração do CMake e evitará erros de incompatibilidade entre as configurações.

Depurando no Visual Studio Code

Podemos depurar o código com GDB ou LLDB usando a interface do Visual Studio Code.

Após construir o projeto com build type Debug ou RelWithDebInfo, selecione a opção “Run” (Ctrl+Shift+D ou botão na barra de atividades) e então a opção “create a launch.json file” para criar um arquivo launch.json no subdiretório .vscode da pasta do projeto:

Em “Select Environment,” selecione “C++ (GDB/LLDB).” Isso criará uma configuração inicial para o arquivo json:

No arquivo launch.json, modifique o valor da chave program para apontar para o executável que se deseja depurar. Por exemplo, ${workspaceFolder}/build/bin/helloworld/helloworld para apontar para o executável do “Hello, World!”

Observação

${workspaceFolder} é uma variável pré-definida do Visual Studio Code que contém o caminho da pasta do projeto. Consulte a documentação para informações sobre outras variáveis disponíveis.

Modifique o valor da chave miDebuggerPath para o caminho do executável do GDB ou LLDB, ou deixe vazio para usar o padrão do sistema. No Windows, a chave miDebuggerPath deve conter explicitamente o caminho completo para o GDB, que é C:\msys64\mingw64\bin\gdb.exe caso o MSYS2 tenha sido instalado em C:\msys64. No Windows também é necessário configurar o terminal padrão do VS Code para “Command Prompt” no lugar de “PowerShell.”

O exemplo abaixo mostra o conteúdo completo de launch.json para depurar o “Hello, World!” no Windows.

{
  // Use IntelliSense to learn about possible attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "(gdb) Launch",
      "type": "cppdbg",
      "request": "launch",
      "program": "${workspaceFolder}/build/bin/helloworld/helloworld.exe",
      "args": [],
      "stopAtEntry": false,
      "cwd": "${workspaceFolder}",
      "environment": [],
      "externalConsole": true,
      "MIMode": "gdb",
      "miDebuggerPath": "C:\\msys64\\mingw64\\bin\\gdb.exe",
      "setupCommands": [
        {
          "description": "Enable pretty-printing for gdb",
          "text": "-enable-pretty-printing",
          "ignoreFailures": true
        }
      ]
    }
  ]
}

Observe que o valor da chave externalConsole foi modificado para true para que um terminal de saída seja aberto durante a depuração. Consulte a documentação sobre depuração para informações sobre outras opções e informações gerais sobre como depurar código no editor.

Após modificar o arquivo launch.json, selecione novamente a opção “Run” na barra de atividades ou tecle F5 para iniciar o programa no modo de depuração.

Reedite o arquivo launch.json sempre que mudar o nome do executável que se queira depurar.

Compilando para WebAssembly

Podemos compilar as aplicações ABCg para WebAssembly de modo a rodá-las diretamente no navegador. A construção é feita via linha de comando usando o toolchain Emscripten. Acompanhe a seguir como construir o exemplo “Hello, World!” para WebAssembly e abri-lo no navegador:

  1. Em um terminal (shell ou Prompt de Comando), ative as variáveis de ambiente do Emscripten (script emsdk_env.sh/emsdk_env.bat do SDK). Após isso, o compilador emcc deverá estar visível no PATH;
  2. No diretório abcg, execute build-wasm.sh (Linux/macOS) ou build-wasm.bat (Windows). Isso iniciará a configuração do CMake e a construção dos binários. Os arquivos resultantes serão gerados no subdiretório public: nesse caso, helloworld.data (arquivo de dados/assets), helloworld.js (arquivo JavaScript) e helloworld.wasm (binário WebAssembly);
  3. Execute o script runweb.sh (Linux/macOS) ou runweb.bat (Windows) para rodar um servidor web local. O conteúdo de public estará disponível em http://localhost:8080/;
  4. Abra a página http://localhost:8080/helloworld.html que chama o script helloworld.js recém-criado. A página HTML não faz parte do processo de construção e foi criada previamente.

O resultado será semelhante ao exibido a seguir (uma aplicação mostrando um triângulo colorido e uma caixa de diálogo com alguns controles de interface). A pequena janela de texto abaixo da janela da aplicação mostra o conteúdo do terminal. Nesse caso, são exibidas algumas informações sobre o OpenGL (versão utilizada, fornecedor do driver, etc).

Observação

O subdiretório public contém, além do helloworld.html:

  • full_window.html: para exibir o “Hello, World!” ocupando a janela inteira do navegador;
  • full_window_console.html: idêntico ao anterior, mas com a sobreposição das mensagens do console na tela.

Nos próximos capítulos veremos como construir novas aplicações usando a ABCg.

Dica

Aproveite o restante da primeira semana de aula para se familiarizar com os conceitos do chamado “C++ moderno” (C++11 em diante): ponteiros inteligentes (smart pointers), expressões lambda, variáveis auto e semântica de movimentação (move semantics). Isso facilitará o entendimento do código da ABCg nos próximos capítulos.