Vulkan. Guia do desenvolvedor

Trabalho como tradutor técnico para a empresa de TI de Izhevsk CG Tribe, que me convidou a contribuir com a comunidade e começar a publicar traduções de artigos e guias interessantes.



Aqui vou postar a tradução do manual da API Vulkan. Link da fonte - vulkan-tutorial.com . Como outro usuário do Habr, kiwhy (https://habr.com/ru/users/kiwhy/), está empenhado na tradução do mesmo manual, concordamos em

compartilhar as lições entre nós. Em minhas publicações, fornecerei links para capítulos traduzidos por kiwhy.



Conteúdo
1.



2.



3.



4.



  1. (pipeline)


5.



  1. Staging


6. Uniform-



  1. layout
  2. sets


7.



  1. Image view image sampler
  2. image sampler


8.



9.



10. -



11. Multisampling



FAQ







1. Introdução



Veja o artigo do autor kiwhy - habr.com/ru/post/462137



2. Visão geral



Antecedentes de Vulkan



Como desenhar um triângulo?



  1. Etapa 1 - Instância e dispositivos físicos
  2. Etapa 2 - Unidade lógica e famílias de fila
  3. Etapa 3 - Superfície da janela e correntes de troca
  4. Etapa 4 - visualizações de imagem e framebuffers
  5. Passo 5 - Render Passes
  6. Etapa 6 - O pipeline de gráficos
  7. Etapa 7 - Pool de comandos e buffers de comando
  8. Etapa 8 - loop principal
  9. conclusões


Conceitos de API



  1. Padrão de formatação de código
  2. Camadas de validação


Neste capítulo, vamos começar com o Vulkan e ver quais problemas ele pode resolver. Descreveremos as etapas necessárias para criar seu primeiro triângulo. Isso lhe dará uma visão geral do padrão e permitirá que você entenda a lógica por trás do layout dos capítulos subsequentes. Concluímos com uma olhada na estrutura da API Vulkan e casos de uso típicos.



Pré-requisitos para Vulkan



Como APIs gráficas anteriores, Vulkan é concebido como uma abstração de plataforma cruzada sobre a GPU . O principal problema com a maioria dessas APIs é que, durante seu desenvolvimento, elas usaram hardware gráfico limitado a funcionalidade fixa. Os desenvolvedores tiveram que fornecer dados de vértice em um formato padrão e eram totalmente dependentes dos fabricantes de GPU para iluminação e sombras.



Conforme a arquitetura das placas de vídeo se desenvolveu, mais e mais funções programáveis ​​começaram a aparecer nela. Todos os novos recursos precisam ser combinados com APIs existentes de alguma forma. Isso levou a abstrações imperfeitas e muitas hipóteses por parte do driver gráfico sobre como traduzir a intenção do programador em arquiteturas gráficas modernas. Portanto, um grande número de atualizações de driver é lançado para melhorar o desempenho nos jogos. Devido à complexidade de tais drivers, muitas vezes existem discrepâncias entre os fornecedores, por exemplo, na sintaxe adotada para shaders... Além disso, a última década também viu um influxo de dispositivos móveis com hardware gráfico poderoso. As arquiteturas dessas GPUs móveis podem variar muito, dependendo do tamanho e dos requisitos de energia. Um exemplo é a renderização baseada em blocos , que pode fornecer melhor desempenho por meio de melhor controle sobre a funcionalidade. Outra limitação devido à idade da API é o suporte limitado para multithreading, que pode levar a um gargalo no lado da CPU.



Vulkan ajuda a resolver esses problemas porque foi construído do zero para arquiteturas gráficas modernas. Isso reduz a sobrecarga do lado do driver, permitindo que os desenvolvedores descrevam claramente seus objetivos usando uma API detalhada. Vulkan permite que você crie e envie comandos em paralelo em vários threads. Ele também reduz as discrepâncias de compilação entre os sombreadores, mudando para um formato de bytecode padronizado e usando um único compilador. Por fim, o Vulkan reúne os principais recursos das placas de vídeo atuais integrando recursos gráficos e de computação em uma única API.



Como faço para desenhar um triângulo?



Vamos dar uma olhada rápida nas etapas necessárias para desenhar um triângulo. Isso lhe dará uma visão geral do processo. Uma descrição detalhada de cada conceito será dada nos capítulos seguintes.



Etapa 1 - Instância e dispositivos físicos



Trabalhar com Vulkan começa configurando a API Vulkan por meio do VkInstance . Uma instância é criada usando a descrição do programa e quaisquer extensões que você deseja usar. Após a instanciação, você pode consultar qual hardware o Vulkan suporta e selecionar um ou mais VkPhysicalDevices para executar as operações. Você pode perguntar sobre parâmetros como tamanho VRAM e recursos do dispositivo para selecionar os dispositivos que deseja, se preferir usar placas gráficas especializadas.



Etapa 2 - Dispositivo lógico e famílias de fila



Depois de selecionar o dispositivo de hardware apropriado para usar, você precisa criar um VkDevice (dispositivo lógico), onde irá descrever com mais detalhes quais recursos ( VkPhysicalDeviceFeatures ) você usará, por exemplo, renderização para múltiplas janelas de exibição. s (renderização de múltiplas portas de visualização) e flutuadores de 64 bits. Você também precisa estabelecer quais famílias de filas deseja usar. Muitas das operações feitas com Vulkan, como comandos de desenho e operações na memória, são realizadas de forma assíncrona após serem enviadas para VkQueue... As filas são alocadas a partir de uma família de filas, onde cada família oferece suporte a um conjunto específico de operações. Por exemplo, podem existir famílias separadas de filas para operações gráficas, operações de computação e transferências de dados de memória. Além disso, sua disponibilidade pode ser usada como um parâmetro-chave na escolha de um dispositivo físico. Alguns dispositivos habilitados para Vulkan não oferecem recursos gráficos, no entanto, todas as placas gráficas habilitadas para Vulkan modernas geralmente suportam todas as operações de enfileiramento de que precisamos.



Passo 3 - Superfície da janela e cadeias de troca



Se você estiver interessado em mais do que apenas renderização fora da tela, você precisa criar uma janela para exibir as imagens renderizadas. O Windows pode ser criado usando APIs de plataforma nativa ou bibliotecas como GLFW e SDL . Estaremos usando o GLFW para este tutorial, que abordaremos com mais detalhes no próximo capítulo.



Precisamos de mais dois componentes para renderizar na janela do aplicativo: a superfície da janela ( VkSurfaceKHR) e a cadeia de exibição ( VkSwapchainKHR). Preste atenção ao postfixKHRo que denota que esses objetos fazem parte da extensão Vulkan. A API Vulkan é totalmente independente de plataforma, portanto, precisamos usar uma extensão WSI (Window System Interface) padronizada para interagir com o gerenciador de janelas. Surface é uma abstração de janelas de plataforma cruzada para renderização, que normalmente é criada referenciando um identificador de janela nativo, por exemplo, HWNDno Windows. Felizmente, a biblioteca GLFW tem uma função integrada para trabalhar com detalhes específicos da plataforma.



Uma cadeia de exibição é um conjunto de alvos de renderização. Sua tarefa é garantir que a imagem que está sendo renderizada difere daquela exibida na tela. Isso permite que você acompanhe se apenas as imagens renderizadas são exibidas. Cada vez que precisamos criar um quadro, temos que fazer um pedido para a cadeia de show nos fornecer uma imagem para renderizar. Após a criação do quadro, a imagem é retornada à cadeia de exibição para ser exibida na tela em algum ponto. O número de alvos de renderização e condições para exibir imagens acabadas na tela depende do modo atual. Esses modos incluem buffer duplo (vsync) e buffer triplo. Vamos abordá-los no capítulo sobre como criar uma cadeia de shows.



Algumas plataformas permitem renderizar diretamente na tela por meio de extensões VK_KHR_displaye VK_KHR_display_swapchainsem interagir com nenhum gerenciador de janelas. Isso permite que você crie uma superfície que representa a tela inteira e pode ser usada, por exemplo, para implementar seu próprio gerenciador de janelas.



Etapa 4 - Visualizações de imagens e framebuffers



Para desenhar na imagem obtida da cadeia de exibição, temos que envolvê-la em um VkImageView e VkFramebuffer . A visualização da imagem se refere a uma parte específica da imagem usada, e o framebuffer se refere às visualizações da imagem, que são usadas como buffers de cor, profundidade e estêncil. Como pode haver muitas imagens diferentes na cadeia de exibição, criaremos uma visualização de imagem e framebuffer para cada uma delas com antecedência e selecionaremos a imagem necessária durante o desenho.



Etapa 5 -



Passes de renderização Os passes de renderização do Vulkan descrevem o tipo de imagens usadas durante as operações de renderização, como são usadas e como seu conteúdo deve ser tratado. Antes de desenhar o triângulo, dizemos a Vulkan que queremos usar uma única imagem como buffer de cor e que precisamos limpá-la antes de desenhar. Se a passagem de renderização descreve apenas o tipo de imagens usadas como buffers, então o VkFramebuffer realmente associa imagens específicas a esses slots.



Etapa 6 - Pipeline de gráficos O



pipeline de gráficos em Vulkan é configurado criando um objeto VkPipeline . Ele descreve o estado configurável da placa de vídeo, como tamanho da janela de visualização ou operação de buffer de profundidade, bem como o estado programável usando objetos VkShaderModule . Os objetos VkShaderModule são criados a partir do bytecode do sombreador. O driver também precisa especificar quais destinos de renderização serão usados ​​no pipeline. Nós os definimos referenciando a passagem de renderização.



Um dos recursos mais distintos do Vulkan em comparação com as APIs existentes é que quase todas as configurações de pipeline de gráficos do sistema devem ser pré-configuradas. Isso significa que se você deseja alternar para um sombreador diferente ou alterar ligeiramente o layout do vértice, é necessário recriar completamente o pipeline gráfico. Portanto, você terá que criar muitos objetos VkPipeline com antecedência para todas as combinações necessárias para as operações de renderização. Apenas algumas configurações básicas, como tamanho da janela de visualização e cores claras, podem ser alteradas dinamicamente. Todos os estados devem ser descritos explicitamente. Portanto, por exemplo, não há um estado de mistura de cores padrão.



Felizmente, como o processo é mais parecido com a compilação à frente, em vez de compilar em tempo real, o driver tem mais oportunidades de otimização e o desempenho é mais previsível porque mudanças de estado significativas, como mudar para um pipeline gráfico diferente, são especificadas explicitamente.



Etapa 7 - Pool de comandos e buffers de comando



Como mencionado, muitas operações no Vulkan, como operações de desenho, devem ser enfileiradas. Antes de enviar operações, eles devem ser gravados no VkCommandBuffer . Os buffers de comando vêm do VkCommandPool , que está associado a uma família específica de filas. Para desenhar um triângulo simples, precisamos escrever um buffer de comando com as seguintes operações:



  • Iniciar passagem de renderização
  • Vincular pipeline de gráficos
  • Desenhe 3 vértices
  • Fim da passagem de renderização


Como a instância da imagem no framebuffer depende de qual imagem a cadeia de exibição nos fornecerá, precisamos escrever um buffer de comando para cada imagem possível e selecionar aquela de que precisamos durante o desenho. Podemos escrever o buffer de comando todas as vezes para cada quadro, mas isso é menos eficiente.



Etapa 8 - Loop principal



Depois de enviar os comandos de desenho para o buffer de comando, o loop principal parece bastante simples. Primeiro, obtemos a imagem da cadeia de shows com vkAcquireNextImageKHR. Podemos então selecionar o buffer de comando apropriado para essa imagem e executá-lo com vkQueueSubmit . Finalmente, retornamos a imagem para a cadeia de exibição para exibição usando vkQueuePresentKHR.



As operações enviadas para a fila são realizadas de forma assíncrona. Portanto, devemos usar objetos de sincronização - semáforos - para garantir a ordem de inicialização correta. É necessário configurar a execução do buffer de comando de desenho de forma que seja realizada somente após a recuperação da imagem da cadeia de exibição, caso contrário pode surgir uma situação ao iniciarmos a renderização de uma imagem que ainda está sendo lida para exibição na tela. A chamada vkQueuePresentKHR, por sua vez, deve aguardar a conclusão da renderização, para a qual usaremos o segundo semáforo. Ele irá notificar sobre o fim da renderização.



Conclusões



Esta visão geral rápida dá a você uma visão geral do trabalho antes de desenhar seu primeiro triângulo. Na realidade, existem muitas outras etapas. Isso inclui a alocação de buffers de vértice, a criação de buffers uniformes e o carregamento de imagens de textura - todos os quais abordaremos nos próximos capítulos, mas, por enquanto, vamos começar de forma simples. Quanto mais avançamos, mais difícil será o material. Observe que decidimos ir pelo caminho mais complicado, inicialmente incorporando as coordenadas do vértice no sombreador de vértice em vez de usar o buffer de vértice. Essa decisão se deve ao fato de que, para gerenciar os buffers de vértice, primeiro você precisa estar familiarizado com os buffers de comando.



Vamos resumir brevemente. Para desenhar o primeiro triângulo, precisamos:



  • Criar VkInstance
  • Selecione uma placa de vídeo compatível ( VkPhysicalDevice )
  • Crie VkDevice e VkQueue para desenho e exibição
  • Criar janela, superfície de janela e mostrar cadeia
  • Envolva imagens da cadeia de exibição em VkImageView
  • Crie uma passagem de renderização que defina os alvos de renderização e seu uso
  • Criar framebuffer para passagem de renderização
  • Configure o pipeline gráfico
  • Distribuir e escrever comandos de desenho no buffer para cada imagem na cadeia de exibição
  • Renderize os quadros para as imagens recebidas enviando o buffer de comando correto e retornando as imagens para a cadeia de exibição


Apesar de serem muitas etapas, o significado de cada uma delas ficará claro nos próximos capítulos. Se você não conseguir descobrir uma etapa, volte a este capítulo.



Conceitos de API



Este capítulo será concluído com uma visão geral rápida de como as APIs Vulkan são estruturadas em um nível inferior.



Padrão de codificação



Todas as funções, enumerações e estruturas do Vulkan são rotuladas sob um título vulkan.hque está incluído no Vulkan SDK desenvolvido pela LunarG. A instalação do SDK será abordada no próximo capítulo.



Funções são prefixadas vkem minúsculas, tipos enumerados (enum) e estruturas são prefixadas Vke valores enumerados são prefixados VK_. A API faz uso extensivo de estruturas para fornecer parâmetros para funções. Por exemplo, os objetos geralmente são criados de acordo com o seguinte padrão:



imagem



Muitas estruturas no Vulkan exigem que você especifique explicitamente o tipo de estrutura no membro sType. Um membro pNextpode apontar para uma estrutura de extensão e sempre será do tiponullptr... As funções que criam ou destroem um objeto terão um parâmetro VkAllocationCallbacks , que permite usar seu próprio alocador de memória e que no manual também terá um tipo nullptr.



Quase todas as funções retornam VkResult , que é VK_SUCCESSum código de erro. A especificação indica quais códigos de erro cada função pode retornar e o que eles significam.



Camadas de validação



Como mencionado, Vulkan foi projetado para fornecer alto desempenho com baixas cargas de driver. Portanto, inclui recursos de detecção e correção automática de erros muito limitados. Se você cometer um erro, o driver travará ou, pior, continuará funcionando em sua placa gráfica, mas falhará em outras placas gráficas.



Portanto, Vulkan permite que você execute validação avançada usando um recurso conhecido como camadas de validação... Camadas de validação são pedaços de código que podem ser inseridos entre a API e o driver gráfico para executar validação adicional nos parâmetros de função e rastrear problemas de gerenciamento de memória. Isso é conveniente porque você pode iniciá-los durante o desenvolvimento e, em seguida, desabilitá-los completamente ao iniciar o programa, sem nenhum custo adicional. Qualquer um pode escrever suas próprias camadas de validação, mas o Vulkan SDK do LunarG fornece um conjunto padrão que usaremos ao longo do tutorial. Você também precisa registrar uma função de retorno de chamada para receber mensagens de depuração das camadas.



Como as operações em Vulkan são muito detalhadas e as camadas de validação são bastante extensas, será muito mais fácil determinar a causa da tela preta em comparação com OpenGL e Direct3D.



Resta apenas uma etapa antes de começarmos a codificar, que é configurar o ambiente de desenvolvimento.



All Articles