Escuela Técnica Superior de Ingeniería

 

Grado en Ingeniería Informática

Animación por Ordenador

Curso 2025/2026

 

Práctica 2d

Creación de los buffers de comandos

 

Objetivo

 

Añade la definición de los buffers de comandos para lanzar los procesos de renderizado.

 

 

La estructura VkCommandPool

 

Para lanzar un proceso de renderizado (una vez configuradas todas las estructuras anteriores) es necesario crear un buffer de comandos que ejecutar sobre una cola del dispositivo lógico. El primer paso para crear un buffer de comandos es generar un objeto VkCommandPool (un generador de buffers de comandos). Para ello se utiliza la función vkCreateCommandPool().

VkResult vkCreateCommandPool(
  VkDevice                       device,
  const VkCommandPoolCreateInfo* pCreateInfo,
  const VkAllocationCallbacks*   pAllocator,
  VkCommandPool*                 pCommandPool);

Para destruir un objeto VkCommandPool se usa la función vkDestroyCommandPool().

void vkDestroyCommandPool(
  VkDevice                     device,
  VkCommandPool                commandPool,
  const VkAllocationCallbacks* pAllocator);

La configuración del objeto VkCommandPool se introduce en una estructura de tipo VkCommandPoolCreateInfo.

typedef struct VkCommandPoolCreateInfo {
  VkStructureType          sType;
  const void*              pNext;
  VkCommandPoolCreateFlags flags;
  uint32_t                 queueFamilyIndex;
} VkCommandPoolCreateInfo;

El campo sType debe tener el valor VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO.

El campo pNext debe dejarse nulo.

El campo flags puede dejarse a nulo aunque existen definidos otros tres bits que indican si los buffers de comandos a construir tendrán una vida breve (VK_COMMAND_POOL_CREATE_TRANSIENT_BIT), si pueden resetearse para ser reutilizados (VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT) y si son protegidos (VK_COMMAND_POOL_CREATE_PROTECTED_BIT).

El campo queueFamilyIndex se refiere a la familia de colas sobre la que debe funcioinar los buffers de comandos a gestionar por el objeto VkCommandPool .

 

 

La estructura VkCommandBuffer

 

Una vez creado el objeto VkCommandPool, se puede utilizar para crear buffers de comandos. Para ello se utiliza la función vkAllocateCommandBuffers() que permite construir una lista de buffers de comandos.

VkResult vkAllocateCommandBuffers(
  VkDevice                           device,
  const VkCommandBufferAllocateInfo* pAllocateInfo,
  VkCommandBuffer*                   pCommandBuffers);

La configuración de esta función se realiza con una estructura VkCommandBufferAllocateInfo.

typedef struct VkCommandBufferAllocateInfo {
  VkStructureType      sType;
  const void*          pNext;
  VkCommandPool        commandPool;
  VkCommandBufferLevel level;
  uint32_t             commandBufferCount;
} VkCommandBufferAllocateInfo;

El campo sType debe tener el valor VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO.

El campo pNext debe dejarse nulo.

El campo commandPool es una referencia al objeto VkCommandPool que generará los buffers de comandos .

El campo level indica si los buffers serán primarios (VK_COMMAND_BUFFER_LEVEL_PRIMARY) o secundarios (VK_COMMAND_BUFFER_LEVEL_SECONDARY). Los buffers secundarios deben ejecutarse desde buffers primarios.

El campo commandBufferCount indica el número de buffers a crear.

 

 

La clase GECommandContext

 

Para incorporar los buffers de comandos se ha creado una nueva clase denominada GECommandContext que contiene como campos las variables commandPool y commandBuffers. El fichero de cabecera tiene el siguiente contenido.

#pragma once

#include <vulkan/vulkan.h>
#include <vector>
#include "GEGraphicsContext.h"

class GECommandContext
{
public:
  std::vector<VkCommandBuffer> commandBuffers;

  GECommandContext(GEGraphicsContext* gc, uint32_t imageCount);
  void destroy(GEGraphicsContext* gc);

private:
  VkCommandPool commandPool;

  // Métodos de inicialización 
  void createCommandPool(GEGraphicsContext* gc);
  void createCommandBuffers(GEGraphicsContext* gc, uint32_t imageCount);
};

El código de GECommandContext incluye los siguientes métodos:

  • GECommandContext(GEGraphicsContext* gc, uint32_t imageCount): Constructor de la clase que inicializa sus campos.

  • destroy(GEGraphicsContext* gc): Destruye los campos de la clase.

  • createCommandPool(GEGraphicsContext* gc): Crea el objeto VkCommandPool vinculado a la familia de colas gráficas del dispositivo.

  • createCommandBuffers(GEGraphicsContext* gc, uint32_t imageCount): Crea un buffer de comandos para cada imagen a generar en la swapchain.

El código de estos métodos es el siguiente.

#include "GECommandContext.h"

#include <iostream>

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


//
// FUNCIÓN: GECommandContext::GECommandContext(...)
//
// PROPÓSITO: Construye los buffers de comnandos
//
GECommandContext::GECommandContext(GEGraphicsContext* gc, uint32_t imageCount)
{
  createCommandPool(gc);
  createCommandBuffers(gc, imageCount);

  std::cout << "Command buffers created!" << std::endl;
}

//
// FUNCIÓN: GECommandContext::destroy(GEGraphicsContext* gc)
//
// PROPÓSITO: Destruye los buffers de comnandos
//
void GECommandContext::destroy(GEGraphicsContext* gc)
{
  size_t bufferCount = commandBuffers.size();

  vkFreeCommandBuffers(gc->device, commandPool, bufferCount, commandBuffers.data());
  vkDestroyCommandPool(gc->device, commandPool, nullptr);
}

///////////////////////////////////////////////////////////////////////////////////
/////                                                                         /////
/////               Métodos de creación de los componentes                    /////
/////                                                                         /////
///////////////////////////////////////////////////////////////////////////////////

//
// FUNCIÓN: GECommandContext::createCommandPool(GEGraphicsContext* gc)
//
// PROPÓSITO: Crea el command pool vinculado a la familia de colas para gráficos
//
void GECommandContext::createCommandPool(GEGraphicsContext* gc)
{
  VkCommandPoolCreateInfo poolInfo{};
  poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
  poolInfo.queueFamilyIndex = gc->graphicsQueueFamilyIndex;

  if(vkCreateCommandPool(gc->device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS)
  {
    throw std::runtime_error("failed to create command pool!");
  }
}

//
// FUNCIÓN: GECommandContext::createCommandBuffers(...)
//
// PROPÓSITO: Crea los buffers de comandos que se enviarán a la cola gráfica
// El contenido de los buffers incluye la orden de dibujar.
//
void GECommandContext::createCommandBuffers(GEGraphicsContext* gc, 
                                            uint32_t imageCount)
{
  commandBuffers.resize(imageCount);

  VkCommandBufferAllocateInfo allocInfo = {};
  allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
  allocInfo.commandPool = commandPool;
  allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
  allocInfo.commandBufferCount = imageCount;

  if (vkAllocateCommandBuffers(gc->device, &allocInfo, commandBuffers.data()) 
                                                                      != VK_SUCCESS)
  {
    throw std::runtime_error("failed to allocate command buffers!");
  }
}

 

 

Modificaciones de la clase GEApplication

 

La clase GEApplication debe incluir un objeto GECommandContext para incorporar los buffers de comandos a la aplicación gráfica. La cabecera de la clase queda así.

#pragma once

#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include "GEWindowPosition.h"
#include "GEGraphicsContext.h"
#include "GEDrawingContext.h"
#include "GECommandContext.h"

const int WIDTH = 800;
const int HEIGHT = 600;

//
// CLASE: GEApplication
//
// DESCRIPCIÓN: Clase que crea y lanza la aplicación gráfica.
//
class GEApplication
{
public:
  void run();

private:
  GLFWwindow* window;
  GEWindowPosition windowPos;
  GEGraphicsContext* gc;
  GEDrawingContext* dc;
  GECommandContext* cc;

  ...
};


Los métodos modificados de GEApplication son run(), para incluir la creación del objeto GECommandContext, y cleanup(), para incluir la destrucción de este objeto.

//
// FUNCIÓN: GEApplication::run()
//
// PROPÓSITO: Ejecuta la aplicación
//
void GEApplication::run()
{
  this->window = initWindow();
  this->windowPos = initWindowPos();
  this->gc = new GEGraphicsContext(window);
  this->dc = new GEDrawingContext(this->gc, this->windowPos);
  this->cc = new GECommandContext(this->gc, this->dc->getImageCount());

  mainLoop();

  cleanup();
}

//
// FUNCIÓN: GEApplication::cleanup()
//
// PROPÓSITO: Libera los recursos y finaliza la aplicación
//
void GEApplication::cleanup()
{
  cc->destroy(gc);
  dc->destroy(gc);
  delete cc;
  delete dc;
  delete gc;
  glfwDestroyWindow(window);
  glfwTerminate();
}

 

 

Aspecto final

 

El único cambio respecto a la aplicación es el mensaje presentado en consola.

Captura 4