Blog banner

Além da Interface Gráfica

Que mal em ir um pouco mais afundo pode ter, né? Vamos dar uma olhada em como foram meus primeiros passos com a API de computação gráfica, Vulkan.

  • Erick Frederick
  • há 9 meses
  • Desenvolvimento de Jogos

Levando em consideração o meu último post onde me introduzi e descrevi um pouco como começei meu hobby desenvolvimento de jogos, no entanto, percebi que faltava algo.

Desde o começo da minha carreira como desenvolvedor de software eu sempre tive um jeito de me habituar com nova áreas de T.I, que é, começar com o caminho de menos resistência (frameworks e biliotecas) e depois ir me aprofundando para descobrir como tudo aquilo funciona, no meu caso essa abordagem é bem efetiva pois fica mais fácil atrelar os componentes com o resultado.

Contexto

Na superfície, esses são os componentes de um computador responsáveis por tudo que aparece na sua tela:

  • Unidade Central de Processamento (CPU):

    • Muito rápido executando comandos.
  • Unidade de Processamento Gráficos (GPU):

    • Ajuda o CPU, mas com ritmo mais lento;
    • Consegue abordar um maior conjunto de dados por conta da sua maior quantidade de núcleos.

Para controlar o CPU você precisa se comunicar usando linguagem de máquina, no entanto, hoje em dia existem ferramentas que traduzem a linguagem humana para linguagem de máquina, com isso surge-se a pergunta, como se comunicar com a GPU?

Acontece que o CPU (considerando meu conhecimento sobre x86/x64) não ulitliza o GPU automáticamente para ajudar na execução de tarefas, isso é responsibilidade de cada aplicação. Você pode usar o GPU diretamente ulitlizando linguagem de programação de baixo nível ou usar APIs que abstraem esta parte de você.

No momento deste post essas são as principais APIs usadas para o desenvolvimento de jogos:

  • Vulkan;
  • DirectX 11 / 12;
  • Metal;
  • OpenGL.

Mesmo vendo em diversos meios de comunicação que o Vulkan não é a melhor opção para começar, no entanto eu ingorei tudo isso e escolhi ela mesmo assim.

Começando

Começar a trabalhar com Vulkan foi uma experiência realmente excruciante, e na época (1 mês atrás), eu achava que as coisas melhorariam um pouco quando eu visse algo na tela. Eu estava certo, mas minha falta de compreensão de alguns conceitos de gráficos computacionais acabou piorando as coisas por um tempo. Esta foi a primeira vez que aprendi uma nova tecnologia que exigiu cerca de 1.000 linhas de código para finalmente ver o produto do meu trabalho em ação. Estranhamente, isso não me desanimou.

Primeiramente eu estava seguindo o Vulkan Tutorial e consegui codificar o equivalente do "Olá Mundo" do desenvolvimento de jogos O Triângulo

Eu realmente gosto de me virar enquanto aprendo uma nova tecnologia, mas quando se trata de um novo campo, eu prefiro adotar uma abordagem mais acadêmica que ensine os fundamentos, para que eu possa ter uma base sólida.

Rodando no automático

Minha falta de conhecimento na computação gráfica me alcançou e me vi copiando e colando código sem pensar e não entendo o que estava fazendo, isso veio a tona quando eu estava com dificuldades para colocar um quadrado na tela e não conseguia entender meu própio código.

O material que eu estava ultilizando explica o como e por que eu estava fazendo algo, mas uma fonte só vai até um certo ponto, com eram colocados redirecionamentos para explicações mais detalhadas quais eu ignorava por conta da minha fixação em produzir algo mais palpável, então decidi procurar como a renderização funciona no Vulkan, é então tive meu primeiro contato com o canal do youtube GetIntoGameDev e sua série de Vulkan com C++.

Para mim foi perfeito, até aquele ponto eu não tinha concecimento que havia a possibilidade em usar Vulkan com C++ e a forma de ensino do GetIntoGameDev tem explicações mais profundas do lado da computação gráfica.

Recomençando

Com isso decidi trocar minha abordagem, rescrever todo meu código até em então e continuar seguindo o material mas escrevendo do meu jeito. Atualmente estou começando a gosta de jogos de corrida mais específicamente Simulation Racing (Sim Racing) e o tema do projeto não podia se outro e assim coloquei o nome de Chicane.

Com o conhecimento que tinha na época eu decidi fazer um esboço do design da engine:

Mesmo que seja um design básico, também é bastante ousado, considerando que eu não vou construir um jogo primeiro, mas sim um Editor que é essencialmente uma ferramenta para criar jogos.

Esse design centralizaria todo o uso da API Vulkan dentro do Runtime, deixando o Editor apenas para adicionar, modificar e excluir instâncias dentro do espaço de renderização, também conhecido como Cenas. E assim eu continuei. Agora, entendendo um pouco melhor minha base de código, eu não me perdia mais. Naquela altura, já havia superado o que fiz anteriormente com a implementação de Textura, Perspectiva e Câmeras.

Mas isso não significa que tudo estava bem, houve alguns contratempos, como tentar rederizar modelos 3D.

Mesmo sem saber o resultado esperado qualquer pessoa pode identificar o formato e também que os triângulos estavam desalinhados ou toltamente ausentes, e isso foi uma dor de cabeça enorme. O culpado?

    outAllocationSize =+ sizeof(*mesh[0]) * allocationInfo.vertexCount;

Este pedaço de código não gerava erros ou alertas ao compilar pois tecnicamente tanto a semântica e sintaxe eram válida. A resolução?

De

    =+

Para

    +=

Debugar e refatorar foram as únicas atividades que fiz por quase três dias. Com o conhecimento maior que tenho da minha base de código, foi possível encontrar e corrigir esse bug irritante. Com isso resolvido, continuei com o tutorial. Há muitas coisas que considerei subotimizadas e refatorei do meu jeito, como não usar um único vetor de floats para declarar dados de vértices, em vez disso, usei uma abordagem mais legível, devido ao meu compromisso com o editor.

Dele

    std::vector<float> vertexData = {
        texturePosX, texturePosY,
        positionX, positionY, positionZ,
        colorR, colorG, colorB,
        normalPositionX, normalPositionY, normalPositionZ
    };

Meu

    struct Instance
    {
        glm::vec3 position;
        glm::vec3 color;
        glm::vec2 texturePosition;
        glm::vec3 normal;
    };

Vale ressaltar que, fazendo isso algumas implementações ficam um pouco mais complexas, mas mudanças como essas se provaram ser úteis enquanto eu continuava com o tutorial, algumas dessas mudanças apareceram como refatorações similares mais na frente das séries, e estou muito orgulhoso de mim mesmo por isso.

O futuro

Atualmente este o estado do projeto.

Desde o início, eu estava verificando de forma moderada as mudanças de desempenho a cada grande alteração no código. Recentemente, fiquei preso tentando corrigir um vazamento de memória relacionado ao manuseio de eventos da janela, como redimensionamento, minimização e maximização.

Ainda não sei se vou tratar esse projeto como experimento ou realmente me concentrar nele, só o tempo dirá...

Por ora é isso. Ainda há algumas coisas que preciso implementar para "completar" o tutorial, como cubemaps e outros. O ano está chegando ao fim, e eu preciso de um tempo de descanso.

Feliz ano novo!