|
Grado en Ingeniería Informática
Animación por Ordenador
Curso 2025/2026
|
Práctica 2c
|
|
Sincronización del proceso de renderizado
|
|
Objetivo
|
|
Añadir semáforos y vallas (fences) al swapchain para
sincronizar el proceso de generacioón de imágenes y crear las colas
(queues) para enviar los comandos de generación y
presentación de las imágenes.
|
|
La sincronización del proceso
de renderizado
|
|
Para generar cada una de las imágenes del swapchain hay que lanzar la
ejecución de un buffer de comandos asociado a cada una de ellas.
La ejecución de un buffer de comandos es un proceso asíncrono.
Esto quiere decir que la función de lanzamiento devuelve el control
sin que realmente el proceso de renderizado se haya completado. El
problema es que no podemos lanzar un proceso de renderizado sobre una
imagen sin asegurarnos de que el renderizado anterior haya finalizado,
es decir, hay que incluir mecanismos de sincronización. En Vulkan esta
sincronización es responsabilidad del programador.
Hay dos formas de sincronizar eventos de la swapchain: semáforos (semaphores)
y vallas (fences). Un semáforo permite definir puntos de sincronización internos
en el dispositivo lógico. Esto permite sincronizar los procesos que se ejecutan
de manera asíncrona en la GPU. Las vallas permiten definir puntos de sincronización
entre la GPU y la CPU.
Para representar una imagen sobre una superficie la GPU debe realizar dos pasos:
generar la imagen y presentarla en la superficie. Para sincronizar esto se definen
dos semáforos, uno para indicar que la imagen está disponible para renderizar sobre
ella y otro para indicar que la imagen está renderizada y está disponible para presentarla.
Para generar un semáforo se utiliza la función
vkCreateSemaphore(). La estructura
VkSemaphoreCreateInfo solo tiene los campos sType,
pNext y flags
que por el momento no tienen contenido
VkResult vkCreateSemaphore (
VkDevice device,
const VkSemaphoreCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkSemaphore* pSemaphore);
typedef struct VkSemaphoreCreateInfo {
VkStructureType sType;
const void* pNext;
VkSemaphoreCreateFlags flags;
} VkSemaphoreCreateInfo;
|
Para sincronizar la presentación de imágenes sobre la swapchain se utilizan
vallas (fences). Solo se va a lanzar un proceso de
presentación de una
imagen si su valla está abierta. Solo abrimos la valla cuando hemos
terminado de presentar la imagen en una pasada anterior.
Para crear una valla se utiliza la función vkCreateFence().
La estructura VkFenceCreateInfo por el momento solo tiene los
campos sType, pNext y flags sin contenido.
VkResult vkCreateFence (
VkDevice device,
const VkFenceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkFence* pFence);
typedef struct VkFenceCreateInfo {
VkStructureType sType;
const void* pNext;
VkFenceCreateFlags flags;
} VkFenceCreateInfo;
|
El proceso gráfico necesita generar las imágenes (un proceso interno
de la GPU) y presentarlas en la superficie (un proceso de
comunicación entre la GPU y la CPU). Estos procesos se realizan
enviando buffers de comandos a una cola de la GPU. Se necesitan, por
tanto, dos colas diferentes: una para enviar la orden de generar la
imagen y otra para enviar la orden de presentar la imagen. Las colas
se obtienen del dispositivo mediante la función vkGetDeviceQueue().
VkResult vkGetDeviceQueue(
VkDevice device,
uint32_t queueFamilyIndex,
uint32_t queueIndex,
VkQueue* pQueue);
|
|
|
Modificaciones de la clase
GEDrawingContext
|
|
Los semáforos, las vallas y las colas se van a definir como campos
de la clase GEDrawingContext. Para completar el proceso de
sincronización se han añadido, además, algunos campos auxiliares.
El número de imágenes que se van a tratar de forma sincronizada es
frameCount. Este parámetro se utilizará para crear el
número adecuado de semáforos y vallas. Los nuevos campos de la clase
son los sighuientes:
-
imageAvailableSemaphores: Vector de semáforos
utilizados para indicar que las imágenes de la swapchain están
disponibles para ser generadas. El número de semáforos es
frameCount.
-
renderFinishedSemaphores: Vector de semáforos
utilizados para indicar que las imágenes de la swapchain están
generadas y pendientes de presentación. El número de semáforos
es frameCount.
-
inFlightFences: Vector de vallas utilizadas para
indicar que las imágenes estámn preparadas para ser presentadas.
El número de vallas es frameCount.
-
imagesInFlight: Vector de vallas utilizado para
relacionar cada imagen con la valla asociada en cada momento. El
número de elenentos es imageCount. Para asociar una
valla a una imagen se almacena la valla (elemento de
inFlightFences) en la posición de imagesInFlight
correspondiente a la imagen de la swapchain.
-
frameCount: Número de imágenes a tratar en paralelo. Se
va a tomar como (imageCount -1).
-
currentFrame: Índice de los semáforos y vallas a
utilizar con la imagen a generar.
-
currentImage:
Índice de la imagen de la swapchain a generar.
-
graphicsQueue: Cola utilizada para enviar comandos de
generación de imágenes.
-
presentQueue: Cola utilizada para enviar comandos de
presentación de imágenes.
El fichero de cabecera queda así.
#pragma once
#include <vulkan/vulkan.h>
#include <vector>
#include "GEGraphicsContext.h"
#include "GEWindowPosition.h"
class GEDrawingContext
{
public:
std::vector<VkImageView> imageViews;
private:
// Campos auxiliares
uint32_t imageCount;
uint32_t frameCount;
size_t currentFrame = 0;
uint32_t currentImage = 0;
// Componentes gráficos
VkSwapchainKHR swapChain;
VkFormat imageFormat;
VkExtent2D imageExtent;
std::vector<VkImage> images;
VkQueue graphicsQueue;
VkQueue presentQueue;
// Sincronización entre imágenes
std::vector<VkSemaphore> imageAvailableSemaphores;
std::vector<VkSemaphore> renderFinishedSemaphores;
std::vector<VkFence> inFlightFences;
std::vector<VkFence> imagesInFlight;
public:
GEDrawingContext(GEGraphicsContext* gc, GEWindowPosition wpos);
void destroy(GEGraphicsContext* gc);
void recreate(GEGraphicsContext* gc, GEWindowPosition wpos);
VkFormat getFormat();
VkExtent2D getExtent();
uint32_t getImageCount();
uint32_t getCurrentImage();
private:
// Métodos de creación de componentes
void createSwapChain(GEGraphicsContext* gc, GEWindowPosition wpos);
void createImageViews(VkDevice device);
void createSyncObjects(VkDevice device);
void createQueues(GEGraphicsContext* gc);
// Métodos auxiliares
VkSurfaceFormatKHR chooseSwapSurfaceFormat(
const std::vector<VkSurfaceFormatKHR>& availableFormats);
VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities,
GEWindowPosition wpos);
};
|
El código de GEDrawingContext incluye modificaciones en el
constructor para construir los semáforos, vallas y colas necesarios para la
sincronización y en el destructor para añadir la destrucción de los
semáforos, vallas y colas creadas, así como los métodos específicos
para crear estos componentes.
///////////////////////////////////////////////////////////////////////////////////
///// /////
///// Métodos públicos /////
///// /////
///////////////////////////////////////////////////////////////////////////////////
//
// FUNCIÓN: GEDrawingContext::GEDrawingContext(GEGraphicsContext* gc)
//
// 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);
createSyncObjects(gc->device);
createQueues(gc);
std::cout << "Semaphores, fences and queues created!" << std::endl;
}
//
// FUNCIÓN: GEDrawingContext::destroy(GEGraphicsContext* gc)
//
// PROPÓSITO: Destruye los componentes del contexto de dibujo
//
void GEDrawingContext::destroy(GEGraphicsContext* gc)
{
for (size_t i = 0; i < frameCount; i++)
{
vkDestroySemaphore(gc->device, renderFinishedSemaphores[i], nullptr);
vkDestroySemaphore(gc->device, imageAvailableSemaphores[i], nullptr);
vkDestroyFence(gc->device, inFlightFences[i], nullptr);
}
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);
}
//
// FUNCIÓN: GEDrawingContext::getCurrentImage()
//
// PROPÓSITO: Obtiene el índice de la imagen a generar
//
uint32_t GEDrawingContext::getCurrentImage()
{
return currentImage;
}
///////////////////////////////////////////////////////////////////////////////////
///// /////
///// Métodos de creación de los componentes /////
///// /////
///////////////////////////////////////////////////////////////////////////////////
//
// FUNCIÓN: GEDrawingContext::createSyncObjects(VkDevice device)
//
// PROPÓSITO: Crea los semáforos y los fences para no sobreescribir las imágenes
//
void GEDrawingContext::createSyncObjects(VkDevice device)
{
frameCount = imageCount - 1;
imageAvailableSemaphores.resize(frameCount);
renderFinishedSemaphores.resize(frameCount);
inFlightFences.resize(frameCount);
imagesInFlight.resize(images.size(), VK_NULL_HANDLE);
VkSemaphoreCreateInfo semaphoreInfo = {};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
VkFenceCreateInfo fenceInfo = {};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
for (size_t i = 0; i < frameCount; i++)
{
if (vkCreateSemaphore(device, &semaphoreInfo, nullptr,
&imageAvailableSemaphores[i]) != VK_SUCCESS ||
vkCreateSemaphore(device, &semaphoreInfo, nullptr,
&renderFinishedSemaphores[i]) != VK_SUCCESS ||
vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS)
{
throw std::runtime_error("failed to create synchronization objects!");
}
}
}
//
// FUNCIÓN: GEDrawingContext::createQueues(GEGraphicsContext* gc)
//
// PROPÓSITO: Obtiene las colas del dispositivo para enviar los comandos de
// generación y presentación de los gráficos
//
void GEDrawingContext::createQueues(GEGraphicsContext* gc)
{
vkGetDeviceQueue(gc->device, gc->graphicsQueueFamilyIndex, 0, &graphicsQueue);
vkGetDeviceQueue(gc->device, gc->presentQueueFamilyIndex, 0, &presentQueue);
}
|
|
|
Aspecto final
|
|
El aspecto de la aplicación sigue siendo una ventana vacía. En la
consola se ofrece el mensaje de creación de los semáforos, vallas y
colas.
|
|