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
5.
6. Uniform-
7.
8.
9.
10. -
11. Multisampling
FAQ
2. Breve descripci贸n general
3. Configuraci贸n del entorno
4. Dibuja un tri谩ngulo
5.
- Staging
6. Uniform-
- layout
- sets
7.
- Image view image sampler
- 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!
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 hacer
cleanup
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 ++