Vulkan. Gu铆a del desarrollador. Dibuja un triangulo

Soy traductor en CG Tribe en Izhevsk y sigo cargando traducciones del manual de la API de Vulkan. Enlace fuente: vulkan-tutorial.com .



Esta publicaci贸n est谩 dedicada a la traducci贸n de la secci贸n Dibujar un tri谩ngulo, es decir, la subsecci贸n Configuraci贸n, los cap铆tulos C贸digo base e Instancia.



Contenido
1. Introducci贸n



2. Breve descripci贸n general



3. Configuraci贸n del entorno



4. Dibuja un tri谩ngulo





  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









C贸digo base







Estructura general



En el cap铆tulo anterior, cubrimos c贸mo crear un proyecto para Vulkan, configurarlo correctamente y probarlo usando un fragmento de c贸digo. En este cap铆tulo, comenzaremos con lo b谩sico.



Considere el siguiente c贸digo:



#include <vulkan/vulkan.h>

#include <iostream>
#include <stdexcept>
#include <cstdlib>

class HelloTriangleApplication {
public:
    void run() {
        initVulkan();
        mainLoop();
        cleanup();
    }

private:
    void initVulkan() {

    }

    void mainLoop() {

    }

    void cleanup() {

    }
};

int main() {
    HelloTriangleApplication app;

    try {
        app.run();
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}
      
      





Primero, incluimos el archivo de encabezado Vulkan del SDK de LunarG. Los archivos de encabezado stdexcepts



y iostream



se utilizan para el manejo y distribuci贸n de errores. El archivo de encabezado cstdlib



proporciona macros EXIT_SUCCESS



y EXIT_FAILURE



.



El programa en s铆 est谩 envuelto en la clase HelloTriangleApplication, en la que almacenaremos los objetos Vulkan como miembros privados de la clase. All铆 tambi茅n agregaremos funciones para inicializar cada objeto, llamado desde la funci贸n initVulkan



. Despu茅s de eso, creemos un bucle principal para renderizar cuadros. Para hacer esto, complete una funci贸n mainLoop



donde se ejecutar谩 el ciclo hasta que se cierre la ventana. Despu茅s de cerrar la ventana y salir, los mainLoop



recursos deben liberarse. Para hacer esto, complete cleanup



.



Si ocurre un error cr铆tico durante la operaci贸n, lanzaremos una excepci贸n std::runtime_error



que quedar谩 atrapada en la funci贸n main



y la descripci贸n se mostrar谩 en formato std::cerr



. Uno de esos errores podr铆a ser, por ejemplo, un mensaje de que la extensi贸n requerida no es compatible. Para manejar muchos de los tipos est谩ndar de excepciones, capturamos una m谩s general std::exception



.



Casi todos los cap铆tulos posteriores agregar谩n nuevas funciones que se llaman desde initVulkan



y nuevos objetos Vulkan que deben liberarse al cleanup



final del programa.



Administracion de recursos



Si los objetos Vulkan ya no son necesarios, deben destruirse. C ++ le permite desasignar recursos autom谩ticamente mediante RAII o punteros inteligentes proporcionados por el archivo de encabezado <memory>



. Sin embargo, en este tutorial decidimos escribir expl铆citamente cu谩ndo asignar y desasignar objetos Vulkan. Despu茅s de todo, esta es la peculiaridad del trabajo de Vulkan: describir en detalle cada operaci贸n para evitar posibles errores.



Despu茅s de leer el tutorial, puede implementar la administraci贸n autom谩tica de recursos escribiendo clases C ++ que reciben objetos Vulkan en el constructor y los liberan en el destructor. Tambi茅n puede implementar su propio eliminador para std::unique_ptr



o std::shared_ptr



, seg煤n sus requisitos. El concepto RAII se recomienda para programas m谩s grandes, pero es 煤til aprender m谩s sobre 茅l.



Los objetos Vulkan se crean directamente usando una funci贸n como vkCreateXXX , o se asignan a trav茅s de otro objeto usando una funci贸n como vkAllocateXXX . Despu茅s de asegurarse de que el objeto no est茅 en uso en ning煤n otro lugar, debe destruirlo con vkDestroyXXX o vkFreeXXX . Los par谩metros para estas caracter铆sticas por lo general var铆an en funci贸n del tipo de objeto, pero hay un par谩metro com煤n: pAllocator



. Este es un par谩metro opcional que le permite utilizar devoluciones de llamada para la asignaci贸n de memoria personalizada. No lo necesitaremos en el manual, lo pasaremos como argumento nullptr



.



Integraci贸n GLFW



Vulkan funciona bien sin crear una ventana cuando se usa el renderizado fuera de la pantalla, pero mucho mejor cuando el resultado es visible en la pantalla.

Primero, reemplace la l铆nea con lo #include <vulkan/vulkan.h>



siguiente:



#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
      
      





Agregue una funci贸n initWindow



y agregue su llamada desde el m茅todo run



antes que otras llamadas. Usaremos initWindow



GLFW para inicializar y crear una ventana.



void run() {
    initWindow();
    initVulkan();
    mainLoop();
    cleanup();
}

private:
    void initWindow() {

    }
      
      





La primera llamada a initWindow



debe ser una funci贸n glfwInit()



que inicialice la biblioteca GLFW. GLFW fue dise帽ado originalmente para funcionar con OpenGL. No necesitamos un contexto OpenGL, as铆 que indique que no necesitamos crearlo usando la siguiente llamada:



glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
      
      





Desactive temporalmente la capacidad de cambiar el tama帽o de la ventana, ya que manejar esta situaci贸n requiere una consideraci贸n por separado:



glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
      
      





Queda por crear una ventana. Para hacer esto, agregue un miembro privado GLFWwindow* window;



e inicialice la ventana con:



window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr);
      
      





Los primeros tres par谩metros definen el ancho, alto y t铆tulo de la ventana. El cuarto par谩metro es opcional, le permite especificar el monitor en el que se mostrar谩 la ventana. El 煤ltimo par谩metro es espec铆fico de OpenGL.



Ser铆a bueno usar constantes para el ancho y alto de la ventana, ya que necesitaremos estos valores en otros lugares. Agregue las siguientes l铆neas antes de la definici贸n de clase HelloTriangleApplication



:



const uint32_t WIDTH = 800;
const uint32_t HEIGHT = 600;
      
      





y reemplace la llamada para crear una ventana con



window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
      
      





Deber铆as tener la siguiente funci贸n initWindow



:



void initWindow() {
    glfwInit();

    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

    window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
}


      
      





Describamos el ciclo principal en el m茅todo mainLoop



para mantener la aplicaci贸n ejecut谩ndose hasta que se cierre la ventana:



void mainLoop() {
    while (!glfwWindowShouldClose(window)) {
        glfwPollEvents();
    }
}
      
      





Este c贸digo no deber铆a plantear ninguna pregunta. Maneja eventos como presionar el bot贸n X antes de que el usuario cierre la ventana. Tambi茅n desde este bucle llamaremos a una funci贸n para renderizar cuadros individuales.



Despu茅s de cerrar la ventana, necesitamos liberar recursos y salir de GLFW. Primero, agreguemos al cleanup



siguiente c贸digo:



void cleanup() {
    glfwDestroyWindow(window);

    glfwTerminate();
}
      
      





Como resultado, despu茅s de iniciar el programa, ver谩 una ventana con un nombre Vulkan



que se mostrar谩 hasta que se cierre el programa. Ahora que tenemos un esqueleto para trabajar con Vulkan, 隆pasemos a crear nuestro primer objeto Vulkan!



C贸digo C ++











Ejemplo







Instanciaci贸n



Lo primero que debe hacer es crear una instancia para inicializar la biblioteca. Una instancia es el v铆nculo entre su programa y la biblioteca Vulkan, y para crearla, deber谩 proporcionar al controlador cierta informaci贸n sobre su programa.



Agregue un m茅todo createInstance



y ll谩melo desde una funci贸n initVulkan



.



void initVulkan() {
    createInstance();
}
      
      





Agregue un miembro de instancia a nuestra clase para mantener un identificador de instancia:



private:
VkInstance instance;
      
      





Ahora necesitamos completar una estructura especial con informaci贸n sobre el programa. T茅cnicamente, los datos son opcionales, sin embargo, esto permitir谩 al conductor obtener informaci贸n 煤til para optimizar el trabajo con su programa. Esta estructura se llama VkApplicationInfo



:



void createInstance() {
    VkApplicationInfo appInfo{};
    appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
    appInfo.pApplicationName = "Hello Triangle";
    appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
    appInfo.pEngineName = "No Engine";
    appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
    appInfo.apiVersion = VK_API_VERSION_1_0;
}
      
      





Como se mencion贸, muchas estructuras en Vulkan requieren una definici贸n de tipo expl铆cita en el miembro sType . Adem谩s, esta estructura, como muchas otras, contiene un elemento pNext



que le permite brindar informaci贸n para extensiones. Usamos la inicializaci贸n de valor para llenar la estructura con ceros.



La mayor parte de la informaci贸n en Vulkan se transmite a trav茅s de estructuras, por lo que debe completar una estructura m谩s para proporcionar suficiente informaci贸n para crear una instancia. Se requiere la siguiente estructura, le dice al controlador qu茅 extensiones globales y capas de validaci贸n queremos usar. "Global" significa que las extensiones se aplican a todo el programa y no a un dispositivo espec铆fico.



VkInstanceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;
      
      





Los dos primeros par谩metros no plantean dudas. Los dos miembros siguientes definen las extensiones globales necesarias. Como ya sabe, la API de Vulkan es completamente independiente de la plataforma. Esto significa que necesita una extensi贸n para interactuar con el sistema de ventanas. GLFW tiene una pr谩ctica funci贸n incorporada que devuelve una lista de extensiones requeridas.



uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;

glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

createInfo.enabledExtensionCount = glfwExtensionCount;
createInfo.ppEnabledExtensionNames = glfwExtensions;
      
      





Los dos 煤ltimos miembros de la estructura definen qu茅 capas de validaci贸n global incluir. Hablaremos de ellos con m谩s detalle en el pr贸ximo cap铆tulo, as铆 que deje estos valores en blanco por ahora.



createInfo.enabledLayerCount = 0;
      
      





Ahora ha hecho todo lo necesario para crear una instancia. Hacer una llamada vkCreateInstance



:



VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);
      
      





Como regla general, los par谩metros de las funciones para crear objetos est谩n en este orden:



  • Puntero a una estructura con la informaci贸n requerida
  • Puntero al asignador personalizado
  • Puntero a la variable donde se escribir谩 el descriptor del nuevo objeto


Si todo se hace correctamente, el descriptor de instancia se almacenar谩 en instancia . Casi todas las funciones de Vulkan devuelven un valor de VkResult , que puede ser un VK_SUCCESS



c贸digo de error o un c贸digo de error. No necesitamos almacenar el resultado para asegurarnos de que se haya creado la instancia. Usemos un cheque simple:



if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
    throw std::runtime_error("failed to create instance!");
}
      
      





Ahora ejecute el programa para verificar que la instancia se cre贸 correctamente.



Comprobaci贸n de extensiones admitidas



Si miramos la documentaci贸n de Vulkan , podemos encontrar que uno de los posibles c贸digos de error es VK_ERROR_EXTENSION_NOT_PRESENT



. Simplemente podemos especificar las extensiones requeridas y dejar de funcionar si no son compatibles. Esto tiene sentido para extensiones importantes como la interfaz del sistema de ventanas, pero 驴qu茅 pasa si queremos probar las capacidades opcionales?



Para obtener una lista de extensiones admitidas antes de crear una instancia, use la funci贸n vkEnumerateInstanceExtensionProperties... El primer par谩metro de la funci贸n es opcional, te permite filtrar extensiones por una capa de validaci贸n espec铆fica, as铆 que lo dejaremos vac铆o por ahora. La funci贸n tambi茅n requiere un puntero a una variable, donde se escribir谩 el n煤mero de extensiones y un puntero a un 谩rea de memoria donde se debe escribir informaci贸n sobre ellas.



Para asignar memoria para almacenar informaci贸n de extensi贸n, primero necesita saber el n煤mero de extensiones. Deje el 煤ltimo par谩metro en blanco para solicitar el n煤mero de extensiones:



uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
      
      





Asigne una matriz para almacenar informaci贸n de extensi贸n (no se olvide include <vector>



):



std::vector<VkExtensionProperties> extensions(extensionCount);
      
      





Ahora puede solicitar informaci贸n sobre extensiones.



vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
      
      





Cada estructura de VkExtensionProperties contiene el nombre y la versi贸n de la extensi贸n. Se pueden enumerar con un bucle for simple ( \t



aqu铆 est谩 la pesta帽a de sangr铆a):



std::cout << "available extensions:\n";

for (const auto& extension : extensions) {
    std::cout << '\t' << extension.extensionName << '\n';
}
      
      





Puede agregar este c贸digo a una funci贸n createInstance



para obtener m谩s informaci贸n sobre el soporte de Vulkan. Tambi茅n puede intentar crear una funci贸n que verifique si todas las extensiones devueltas por la funci贸n glfwGetRequiredInstanceExtensions



est谩n incluidas en la lista de extensiones compatibles.





Limpieza



VkInstance debe destruirse antes de cerrar el programa. Esto se puede hacercleanup



usando la funci贸n VkDestroyInstance :



void cleanup() {
    vkDestroyInstance(instance, nullptr);

    glfwDestroyWindow(window);

    glfwTerminate();
}
      
      





Los par谩metros de la funci贸n vkDestroyInstance son autoexplicativos. Como se mencion贸 en el cap铆tulo anterior, las funciones de asignaci贸n y desasignaci贸n en Vulkan aceptan punteros opcionales a asignadores personalizados que no usamos y pasamos nullptr



. Todos los dem谩s recursos de Vulkan deben limpiarse antes de que se destruya la instancia.



Antes de pasar a pasos m谩s complejos, debemos configurar las capas de validaci贸n para facilitar la depuraci贸n.



C贸digo C ++



All Articles