Escuela Técnica Superior de Ingeniería

 

Grado en Ingeniería Informática

Animación por Ordenador

Curso 2025/2026

 

Práctica 2b

Creación de vistas

 

Objetivos

 

Añadir al proyecto la creación de vistas sobre las imágenes de la cadena de intercambio de imágenes.

 

 

Vistas

 

Para poder acceder a una imagen y generar su contenido es necesario crear un objeto VkImageView asociado a cada objeto VkImage. Esto quiere decir que para que la aplicación pueda generar las imágenes almacenadas en la swapchain es necesario crear una lista de vistas asociadas a cada imagen.

Para crear una vista se utiliza la función vkCreateImageView().

VkResult vkCreateImageView (
  VkDevice                     device,
  const VkImageViewCreateInfo* pCreateInfo,
  const VkAllocationCallbacks* pAllocator,
  VkImageView*                 pView);

La información necesaria para crear una vista se almacena en una estructura VkImageViewCreateInfo. El contenido de sus campos es el siguiente:

  • El campo sType debe ser VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO.

  • Los campos pNext y flags se han reservado para futuras modificaciones y de momento deben declararse nulos.

  •  El campo image contiene la referencia a la imagen asociada a la vista.

  • El campo viewType indica el tipo de imagen que se quiere editar. Para una imagen asociada a una superficie lo lógico es usar el tipo VK_IMAGE_VIEW_TYPE_2D, pero existen tipos 1D, 2D, 3D, CUBE, 1D_ARRAY, 2D_ARRAY y CUBE_ARRAY que se utilizan en otros ámbitos.

  • El formato de la vista debe ser compatible con el formato de la imagen. Generalmente se usa el mismo, aunque se consideran formatos compatibles si tienen el mismo número de bits por componente.

  • El campo components describe el mapeo a realizar sobre los canales de color. La estructura VkComponentMapping contiene cuatro campos (r,g,b,a) que permiten referenciar estos canales. Estos campos deben recoger valores de la enumeración VkComponentSwizzle. Por ejemplo, el valor VK_COMPONENT_SWIZZLE_IDENTITY indica que el mapeo es al mismo canal, pero también se puede indicar valores como VK_COMPONENT_SWIZZLE_R para referirse a un canal concreto (el R en este caso) o VK_COMPONENT_SWIZZLE_ZERO para indicar un valor fijo (el cero en este caso).

  • El campo subresourceRange describe la forma en la que se va a utilizar la vista de la imagen por medio de una estructura VkImageSubresourceRange.

typedef struct VkImageViewCreateInfo {
  VkStructureType         sType;
  const void*             pNext;
  VkImageViewCreateFlags  flags;
  VkImage                 image;
  VkImageViewType         viewType;
  VkFormat                format;
  VkComponentMapping      components;
  VkImageSubresourceRange subresourceRange;
} VkImageViewCreateInfo;

La estructura VkImageSubresourceRange contiene el campo aspectMask que indica el contenido que se va a almacenar en la imagen. Este campo puede tomar los valores:

  • VK_IMAGE_ASPECT_COLOR_BIT

  • VK_IMAGE_ASPECT_DEPTH_BIT

  • VK_IMAGE_ASPECT_STENCIL_BIT

  • VK_IMAGE_ASPECT_METADATA_BIT

Los campos baseMipLevel and levelCount para seleccionar los niveles de mipmap utilizados y los campos baseArrayLayer  y layerCount para indicar las capas utilizadas.

typedef struct VkImageSubresourceRange {
  VkImageAspectFlags aspectMask;
  uint32_t           baseMipLevel;
  uint32_t           levelCount;
  uint32_t           baseArrayLayer;
  uint32_t           layerCount;
} VkImageSubresourceRange;

 

 

Modificaciones de la clase GEDrawingContext

 

La introducción de las vistas requiere modificar la clase GEDrawingContext para añadir un vector de vistas y un método para crearlas.

class GEDrawingContext
{
public:
  std::vector<VkImageView> imageViews;

private:
  // Campos auxiliares
  uint32_t imageCount;

  // Componentes gráficos
  VkSwapchainKHR swapChain;
  VkFormat imageFormat;
  VkExtent2D imageExtent;
  std::vector<VkImage> images;

  ...
  
private:
  // Métodos de creación de componentes
  void createSwapChain(GEGraphicsContext* gc, GEWindowPosition wpos);
  void createImageViews(VkDevice device);

  ...
};

Los cambios en los métodos incluyen modificaciones en el constructor de la clase para añadir la llamada a la creación de las vistas. Los métodos destroy() y recreate() deben incluir la destrucción y reconstrucción de todas las vistas. El método createImageViews()  se encarga de crear la lista de vistas. Para ello crea un objeto VkImageView para cada una de las imágenes de la swapchain.

///////////////////////////////////////////////////////////////////////////////////
/////                                                                         /////
/////                          Métodos públicos                               /////
/////                                                                         /////
///////////////////////////////////////////////////////////////////////////////////

// FUNCIÓN: GEDrawingContext::GEDrawingContext(...)
//
// PROPÓSITO: Crea el contexto de dibujo (imágenes y cadena de intercambio)
//
GEDrawingContext::GEDrawingContext(GEGraphicsContext* gc, GEWindowPosition wpos)
{
  createSwapChain(gc, wpos);
  createImageViews(gc->device);

  std::cout << "Image views created!" << std::endl;
}

//
// FUNCIÓN: GEDrawingContext::destroy(GEGraphicsContext* gc)
//
// PROPÓSITO: Destruye los componentes del contexto de dibujo
//
void GEDrawingContext::destroy(GEGraphicsContext* gc)
{
  for (uint32_t i = 0; i < imageCount; i++)
  {
    vkDestroyImageView(gc->device, imageViews[i], nullptr);
  }
  vkDestroySwapchainKHR(gc->device, swapChain, nullptr);
}

//
// FUNCIÓN: GEDrawingContext::recreate(GEGraphicsContext* gc, GEWindowPosition wpos)
//
// PROPÓSITO: Reconstruye los componentes del contexto de dibujo
//
void GEDrawingContext::recreate(GEGraphicsContext* gc, GEWindowPosition wpos)
{
  for (uint32_t i = 0; i < imageCount; i++)
  {
    vkDestroyImageView(gc->device, imageViews[i], nullptr);
  }
  vkDestroySwapchainKHR(gc->device, swapChain, nullptr);

  createSwapChain(gc, wpos);
  createImageViews(gc->device);
}

...

///////////////////////////////////////////////////////////////////////////////////
/////                                                                         /////
/////                 Métodos de inicialización de Vulkan                     /////
/////                                                                         /////
///////////////////////////////////////////////////////////////////////////////////

...

//
// FUNCIÓN: GEDrawingContext::createImageViews(VkDevice device)
//
// PROPÓSITO: Crea una vista para cada imagen de la cadena de intercambio
//
void GEDrawingContext::createImageViews(VkDevice device)
{
  imageViews.resize(imageCount);

  for (size_t i = 0; i < imageCount; i++)
  {
    VkImageViewCreateInfo createInfo{};
    createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
    createInfo.image = images[i];
    createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
    createInfo.format = imageFormat;
    createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
    createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
    createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
    createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
    createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    createInfo.subresourceRange.baseMipLevel = 0;
    createInfo.subresourceRange.levelCount = 1;
    createInfo.subresourceRange.baseArrayLayer = 0;
    createInfo.subresourceRange.layerCount = 1;

    if (vkCreateImageView(device, &createInfo, nullptr, &imageViews[i])
                                                                     != VK_SUCCESS)
    {
      throw std::runtime_error("failed to create image views!");
    }
  }
}

 

 

Aspecto final

 

El aspecto de la aplicación sigue siendo una ventana vacía. En la consola se muestra el mensaje de creación de las vistas.

Figura2