|
El buffer de profundidad es necesario para que al dibujar un pixel se
tenga en cuenta si ese píxel se ha rellenado anteriormente como parte
de una primitiva que se encuentre más cerca del observador. Antes de
lanzar el Fragment Shader sobre un pixel se estudia el valor
de profundidad almacenado en las coordenadas de ese pixel, que deben
corresponder a la profundidad almacenada en la ejecución de una
primitiva tratada con anterioridad. Generalmente el test de
profundidad consiste en no lanzar el Fragment Shader si el
pixel a dibujar está más alejado que el pixel ya dibujado. Por tanto,
el buffer de profundidad (depth buffer) es una estructura
paralela a la utilizada para la imagen (color buffer) pero
dedicada a almacenar otro tipo de datos.
En Vulkan existen dos tipos de recursos para almacenar datos: los
objetos VkBuffer y los objetos VkImage. Los
objetos VkBuffer se utilizan para almacenar bloques de
datos sin una estructura prefijada. Es el tipo de recurso que se
utiliza para almacenar los atributos de los vértices, los índices de
vértices que forman las primitivas o los valores de las variables
uniformes de los shaders. Los objetos VkImage
permiten almacenar datos con una estructura que incluye información
sobre su formato y dimensiones y tienen procedimientos específicos
para leer y escribir datos sobre ellos. Los buffers de profundidad
se almacenan en objetos de tipo VkImage ya que deben
tratarse de la misma forma que los color buffer asociados.
Los objetos VkBuffer y VkImage describen los bloques de datos a
almacenar pero no definen la memoria sobre la que serán almacenados.
Para asignar esa memoria es necesario definir objetos VkDeviceMemory
y vincularlos a estos recursos. Para poder usar el objeto
VkImage es necesario crear un vista sobre ese objeto. Las
vistas son objetos VkImageView que contienen información
adicional necesaria para poder interactuar con el objeto VkImage.
Al crear la swapchain se crean de manera automática los
objetos VkImage y VkDeviceMemory que almacenan las
imágenes a presentar en la superficie. Como hemos visto es necesario
crear explicitamente los objetos VkImageView para poder
acceder a estas imágenes. Para utilizar los buffers de profundidad
hay que crear explicitamente los objetos VkImage,
VkDeviceMemory y VkImageView.
Los objetos VkImage se crean con la función vkCreateImage()
que se describe a continuación.
VkResult vkCreateImage (
VkDevice device,
const VkImageCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkImage* pImage);
|
La definición de la estructura VkImageCreateInfo es la siguiente
typedef struct VkImageCreateInfo {
VkStructureType sType;
const void* pNext;
VkImageCreateFlags flags;
VkImageType imageType;
VkFormat format;
VkExtent3D extent;
uint32_t mipLevels;
uint32_t arrayLayers;
VkSampleCountFlagBits samples;
VkImageTiling tiling;
VkImageUsageFlags usage;
VkSharingMode sharingMode;
uint32_t queueFamilyIndexCount;
const uint32_t* pQueueFamilyIndices;
VkImageLayout initialLayout;
} VkImageCreateInfo;
|
Algunos de los campos incluidos en esta estructura coinciden con
campos incluidos en la creación de swapchains ya que son
necesarios para crear las imágenes que forman la swapchain.
Puesto que las imágenes de la swapchain y los buffers de profundidad
son estructuras paralelas muchos de estos campos deben coincidir
entre ellas.
El campo sType debe tener el valor
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO.
Los campos pNext y flags pueden dejarse nulos.
Existen posibles valores para el campo flags pero no son
necesarios para crear buffers de profundidad.
El campo imageType indica el tipo de imagen. Las imágenes
bidimensionales se definen como VK_IMAGE_TYPE_2D.
El campo format indica el formato utilizado para almacenar
la información de cada pixel.
El campo extent almacena el tamaño en pixel de la imagen a
crear. Para las imágenes a presentar y los buffers de profundidad la
tercera componente debe ser 1.
El campo mipLevels especifica el número de niveles de
mipmap de la imagen a crear. En este caso se usa un único nivel.
El campo arrayLayers indica el número de capas de la
imagen. Para los buffers de profundidad se usa una única capa.
El campo tiling indica la forma de posicionar la imagen.
Generalmente se utiliza el valor VK_IMAGE_TILING_OPTIMAL que deja en
manos de la GPU este proceso.
El campo usage describe el uso que se le va a dar a la
imagen. Para utilizarla como buffer de profundidad se usa el valor
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT.
El campo sharingMode indica si el objeto va a ser accesible
desde comandos de diferentes colas (VK_SHARING_MODE_CONCURRENT) o
solo desde una (VK_SHARING_MODE_EXCLUSIVE). Si el acceso va a ser
concurrente hay que indicar cuantas familias de colas van a acceder
a el (queueFamilyIndexCount) y la lista de esas familias (pQueueFamilyIndices).
El campo initialLayout, aunque de tipo VkImageLayout, solo
puede tomar los valores VK_IMAGE_LAYOUT_UNDEFINED or the
VK_IMAGE_LAYOUT_PREINITIALIZED. Para crear los buffers de
profundidad se deja indifinido.
Una vez creado el buffer hay que asignar la memoria del dispositivo
donde se almacenarán sus datos. Para esto hay que crear un objeto
VkDeviceMemory por medio de la función
vkAllocateMemory().
VkResult vkAllocateMemory(
VkDevice device,
const VkMemoryAllocateInfo* pAllocateInfo,
const VkAllocationCallbacks* pAllocator,
VkDeviceMemory* pMemory);
|
Para asignar la memoria hay que configurar el tipo de memoria a
asignar por medio de una estructura VkMemoryAllocateInfo.
typedef struct VkMemoryAllocateInfo {
VkStructureType sType;
const void* pNext;
VkDeviceSize allocationSize;
uint32_t memoryTypeIndex;
} VkMemoryAllocateInfo;
|
El campo sType debe ser
El campo pNext está reservado para un uso futuro.
El campo allocationSize indica el tamaño en bytes de la memoria a
asignar.
El campo memoryTypeIndex expresa el tipo de memoria a asignar. Los
dispositivos gráficos soportan diferentes tipos de memoria que
pueden obtenerse mediante la función vkGetPhysicalDeviceMemoryProperties().
El valor a introducir en este campo corresponde al índice del tipo
de memoria deseada de entre las soportadas por el dispositivo. Para
los buffers de profundidad hay que escoger un tipo de memoria que
sea especialmente rápida para accesos internos del dispositivo. Esto
se corresponde a un tipo de memoria que contenga la propiedad
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT.
Una vez reservada la memoria, es necesario establecer la vinculación
entre el buffer y la memoria por medio de la función
vkBindImageMemory().
VkResult vkBindImageMemory(
VkDevice device,
VkImage image,
VkDeviceMemory memory,
VkDeviceSize memoryOffset);
|
Para terminar de generar la configuración de los buffers de
profundidad hay que crear un objeto VkImageView para
controlar el acceso al buffer. El proceso de creación de vistas se
explicó en el proyecto 2b
|