Link

Pipeline gráfico

Como mencionado anteriormente, para desenhar uma imagem qualquer na tela, precisamos de um pipeline gráfico. No Vulkan, há atualmente um pipeline de computação e um pipeline gráfico. O pipeline de computação nos permite realizar algum trabalho computacional, como a realização de cálculos físicos para objetos em jogos. O pipeline gráfico é usado para operações de desenho.

O pipeline gráfico no Vulkan pode ser visto como uma linha de produção, na qual os comandos entram na frente do pipeline e são processados em estágios. Cada estágio realiza algum tipo de transformação, recebendo os comandos e seus dados associados e transformando-os em dados que serão utilizamos como entrada para o próximo estágio. No final do pipeline gráfico, os comandos são transformados em pixels coloridos que irão compor uma imagem final. Uma visão geral simplificada do pipeline gráfico é mostrada na Figura 6.

Figura 6 – Visão geral simplificada do pipeline gráfico do Vulkan


Fonte: Overvoorde (2018).


Estágios com fundo branco são conhecidos como estágios programáveis, o que significa que podemos enviar nosso próprio código para a GPU para aplicar exatamente as operações desejadas. Isso permite que usemos fragment shader, por exemplo, para implementar qualquer coisa, desde texturização até iluminação. Esses programas são executados em vários núcleos da GPU simultaneamente para processar em paralelo muitos objetos, como vértices e fragmentos.

Por outro lado, os estágios em cinza são conhecidos como estágios de função fixa. Isso significa que a maneira como eles funcionam é predefinida, porém eles nos permitem que nós ajustemos suas operações através de parâmetros.

Muitas partes do pipeline gráfico são opcionais e podem ser desativadas ou até mesmo podem não ser suportadas por alguma implementação do Vulkan. A única parte do pipeline gráfico que um aplicativo deve obrigatoriamente habilitar é o vertex shader1. Alguns dos estágios programáveis são opcionais com base no que pretendemos fazer. Por exemplo, os estágios de tessellation e geometry shader podem ser desativados se estivermos apenas desenhando uma geometria simples.

1 Um vertex shader é sempre necessário; mesmo que nenhuma transformação seja necessária, nesse caso, um shader deve ser fornecido, simplesmente retornando vértices sem modificações.

Os estágios do pipeline gráfico do Vulkan são semelhantes aos estágios do pipeline gráfico do OpenGL. A principal diferença está no nome dado para cada estágio, mas a funcionalidade é equivalente. Portanto, se o leitor já estiver familiarizado com o pipeline gráfico do OpenGL, não terá problemas em entender o pipeline gráfico do Vulkan. Outra importante diferença entre os pipelines gráficos do Vulkan e do OpenGL está na forma como os configuramos. No Vulkan precisamos configurar manualmente todos estágios de função fixa e alguns estágios programáveis que iremos utilizar. Por outro lado, no OpenGL, normalmente, só precisamos configurar manualmente os estágios programáveis do pipeline e alguns estágios de função fixa. Isso porque a maioria do estágios de função fixa possuem uma configuração padrão.

A seguir, uma breve descrição de cada estágio do pipeline gráfico e o que ele faz.

Input assembler

O estágio do input assembler lê dados de primitivas (pontos, linhas e/ou triângulos) dos buffers e os monta em primitivas para uso em etapas subsequentes. Normalmente, um ou mais buffer de vértice (vertex buffers) e, opcionalmente, um index buffer2, são fornecidos como entrada.

2 Index buffers são usados em geometria indexada de modo a reusar vértices compartilhados entre primitivas.

Vertex shader

O vertex shader é executado uma vez para cada vértice que é fornecido pelo input assembler. Geralmente o objetivo principal de um vertex shader é transformar as posições dos vértices de entrada para o espaço de tela. Essa transformação é realizada multiplicando-se o vértice por uma matriz de transformação.

Um vertex shader é capaz de manipular qualquer uma das propriedades de um vértice. Isso inclui, mas não está limitado às seguintes informações:

  • Posições
  • Cores
  • Vetores normais
  • Coordenadas de textura

Como as essas propriedades são manipuladas depende do efeito específico que o vertex shader está preparando para o próximo estágio.

Tessellation

O estágio de tessellation permite que malhas mais simples sejam subdivididas em malhas mais detalhadas no tempo de execução de acordo com uma função matemática. A função pode estar relacionada a uma variedade de variáveis, mais notavelmente a distância da câmera de visualização para permitir escala ativa de nível de detalhe. Isso permite que objetos próximos à câmera tenham detalhes finos, enquanto outros mais afastados podem ter malhas mais grosseiras, mas parecem comparáveis em qualidade. Ele também pode reduzir drasticamente a largura de banda de malha necessária, permitindo que as malhas sejam refinadas uma vez dentro das unidades de shader, em vez de reduzir a resolução de amostras muito complexas da memória.

Geometry shader

O geometry shader executa o código do shader especificado pelo aplicativo com vértices como entrada e a capacidade de gerar vértices na saída. Ao contrário dos vertex shaders, que operam em um único vértice, as entradas do geometry shader são os vértices de uma primitiva completa (dois vértices para linhas, três vértices para triângulos ou um único vértice para ponto). Os geometry shaders também podem trazer os dados de vértice para as primitivas adjacentes ao limite como entrada (um adicional de dois vértices para uma linha, um adicional de três para um triângulo). Isso é semelhante ao estágio de tesselation, mas muito mais flexível. No entanto, ele não é muito usado nos aplicativos atuais porque o desempenho não é tão bom na maioria das placas gráficas (OVERVOORDE, 2018).

Rasterization

O estágio de rasterization (rasterização) converte cada primitiva em fragmentos3, enquanto interpola valores por vértice entre cada primitiva. A rasterização também inclui o recorte de vértices para o tronco de exibição (frustum), mapeamento de primitivas para a tela e determinação de como invocar o fragment shader.

3 Esses são os elementos de pixel que eles preenchem no framebuffer.

Fragment shader

O fragment shader opera em cada fragmento gerado pelo estágio de rasterização. Sabemos que as operações de rasterização interpolam valores como cor, profundidade e coordenada de textura. O fragment shader usa esses valores interpolados, bem como muitos outros tipos de informações, para determinar a cor e profundidade de cada fragmento para um ou mais framebuffers.

Color blending

O estágio final no pipeline gráfico do Vulkan é o estágio de color blending (mistura de cores). Este estágio é responsável por escrever fragmentos nos anexos de cor. Em muitos casos, essa é uma operação simples que simplesmente sobrescreve o conteúdo existente do anexo com valor(es) de saída do fragment shader. No entanto, a mistura de cores é capaz de misturar (mesclar) esses valores com os valores já no framebuffer e realizar operações lógicas simples entre a saída do fragment shader e o conteúdo atual do framebuffer.

Na próxima subseção, primeiro criaremos os dois estágios programáveis necessários para colocar um triângulo na tela: o vertex shader e o fragment shader. As configurações dos estágios de função fixa como input assembler, rasterization e color blending serão tratadas na subseção seguinte.

Parar configurarmos nosso pipeline gráfico vamos criar uma função Renderer::initPipeline() que deve ser chamada na função initResources():

...
void Renderer::initResources() {
    VkDevice device = m_window->device();
    m_deviceFunctions = m_window->vulkanInstance()->deviceFunctions(device);

    initPipeline();
}

void Renderer::initPipeline() {

}

Trabalharemos nessa função nas próximas subseções.


Anterior Próximo