Criando uma image view
Em muitos casos, o recurso de imagem não pode ser usado diretamente, pois mais informações sobre ele são necessárias do que as incluídas no próprio recurso. Por exemplo, não podemos vincular uma imagem a um conjunto de descritores para obter uma amostra dela em um shader. Para satisfazer esses requisitos adicionais, devemos criar uma image view (visualização de imagem) representada no Vulkan por um objeto VkImageView
. Uma image view é essencialmente uma coleção de propriedades e uma referência a um objeto VkImage
.
Uma image view também permite que toda ou parte de uma imagem existente seja vista como um formato diferente. Por exemplo, podemos ter uma imagem multicamadas (array 2D) e queremos renderizar apenas para uma camada de array específica.
Adicionamos um membro de estrutura em Object3D
para manter um objeto VkImageView
para a imagem de textura e criamos uma nova função createTextureImageView
onde criaremos esse objeto. Chamamos essa função no final de addTextureImage
:
...
VkImage textureImage = VK_NULL_HANDLE;
VkDeviceMemory textureImageMemory = VK_NULL_HANDLE;
VkImageView textureImageView = VK_NULL_HANDLE;
...
void Renderer::createTextureImageView() {
}
..
void Renderer::addTextureImage(QString texturePath) {
...
createTextureImageView();
}
Para criar uma image view, precisamos preparar uma estrutura do tipo VkImageViewCreateInfo
:
VkImageViewCreateInfo viewInfo = {};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.pNext = nullptr;
viewInfo.flags = 0;
viewInfo.image = m_object->textureImage;
O campo flags
dessa estrutura é reservado para uso futuro e deve ser definido como 0
. O objeto VkImage
do qual criaremos uma nova image view é especificado em image
.
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
O tipo de visualização a ser criado é especificado em viewType
. O tipo de visualização deve ser compatível com o tipo de imagem e é um membro da enumeração VkImageViewType
, que é maior que a enumeração VkImageType
usada na criação da imagem. Os tipos de exibição de imagem são os seguintes:
VK_IMAGE_VIEW_TYPE_1D
,VK_IMAGE_VIEW_TYPE_2D
eVK_IMAGE_VIEW_TYPE_3D
são os tipos “normais” de imagem 1D, 2D e 3D, respectivamente.VK_IMAGE_VIEW_TYPE_CUBE
eVK_IMAGE_VIEW_TYPE_CUBE_ARRAY
são imagens de mapa de cubo (cube map) e de array de mapa de cubo, respectivamente.
viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
O formato da nova visualização é especificado em format
. Esse deve ser um formato compatível com o da imagem. Em geral, se dois formatos tiverem o mesmo número de bits por pixel, eles serão considerados compatíveis. Estamos utilizando aqui o mesmo formato que a imagem.
viewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
viewInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
viewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
viewInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
A ordem dos componentes na image view pode ser diferente daquela na imagem. Isso permite, por exemplo, criar uma visualização RGBA de uma imagem no formato BGRA. Esse remapeamento é especificado usando uma instância de VkComponentMapping
incorporada em components
. Cada membro de VkComponentMapping
especifica a fonte de dados na imagem que será usada para preencher o texel resultante obtido da image view. Eles são membros da enumeração VkComponentSwizzle
. No nosso caso, queremos que os dados na image view sejam lidos no canal correspondente na imagem. Para isso, usamos o sinalizador VK_COMPONENT_SWIZZLE_IDENTITY
para cada componente.
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
O subconjunto da imagem que queremos visualizar é especificado no campo subresourceRange
. Este é uma instância da estrutura VkImageSubresourceRange
. O campo aspectMask
dessa estrutura é um campo de bits composto por membros da enumeração VkImageAspectFlagBits
, que especifica quais aspectos da imagem desejamos consultar o layout. Alguns tipos de imagens têm mais de uma parte lógica, mesmo que os dados em si possam ser intercalados ou relacionados de alguma forma. Um exemplo disso são as imagens de estêncil e profundidade, que possuem um componente de profundidade e um componente de estêncil. Cada um desses dois componentes pode ser visualizado como uma imagem separada por si só, e essas sub-imagens são conhecidas como aspectos. Os sinalizadores que podem ser incluídos no aspectMask
são:
VK_IMAGE_ASPECT_COLOR_BIT
: A parte colorida de uma imagem. Geralmente, há apenas um aspecto colorido nas imagens coloridas.VK_IMAGE_ASPECT_DEPTH_BIT
: O aspecto de profundidade de uma imagem de estêncil e profundidade.VK_IMAGE_ASPECT_STENCIL_BIT
: O aspecto de estêncil de uma imagem de estêncil e profundidade.VK_IMAGE_ASPECT_METADATA_BIT
: Qualquer informação adicional associada à imagem que possa rastrear seu estado e é usada, por exemplo, em várias técnicas de compactação.
Como estamos tratando aqui somente de imagens coloridas, utilizamos apenas o sinalizador VK_IMAGE_ASPECT_COLOR_BIT
.
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
Para criar uma nova image view que corresponda apenas a um subconjunto da cadeia MIP da imagem, devemos usar baseMipLevel
e levelCount
para especificar onde na cadeia MIP a exibição começa e quantos níveis MIP ela conterá. Se a imagem não tiver mapeamento MIP, como no nosso caso, esses campos deverão ser definidos como 0
e 1
, respectivamente.
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
Da mesma forma, para criar uma image view de um subconjunto das camadas de array de uma imagem, usamos os campos baseArrayLayer
e layerCount
para especificar a camada inicial e o número de camadas, respectivamente. Novamente, se a imagem não for uma imagem de array, baseArrayLayer
deve ser definido como 0
e layerCount
deve ser definido como 1
, como no nosso caso.
A criação do objeto VkImageView
é realizada através de uma única chamada da função vkCreateImageView
:
VkDevice device = m_window->device();
if (m_object->textureImageView) {
m_deviceFunctions->vkDestroyImageView(
device,
m_object->textureImageView,
nullptr
);
}
VkResult result = m_deviceFunctions->vkCreateImageView(
device,
&viewInfo,
nullptr,
&m_object->textureImageView
);
if (result != VK_SUCCESS) {
qFatal("Failed to create texture image view: %d", result);
}
Primeiro verificamos se o objeto VkImageView
já foi criado. Caso já tenha sido criado, destruímos o objeto já criado, antes de chamar vkCreateImageView
.
Destruímos esse objeto VkImageView
em releaseObjectResources
, antes de destruir o objeto de imagem em si:
void Renderer::releaseObjectResources() {
...
if (m_object->textureImageView) {
m_deviceFunctions->vkDestroyImageView(
device,
m_object->textureImageView,
nullptr
);
}
if (m_object->textureImage) {
m_deviceFunctions->vkDestroyImage(
device,
m_object->textureImage,
nullptr
);
m_object->textureImage = VK_NULL_HANDLE;
}
...