|
Grado en Ingeniería Informática
Animación por Ordenador
Curso 2025/2026
|
Práctica 4a
|
|
Introducción de un Vertex Buffer
|
|
Objetivos
|
|
Modificar la aplicación para que los vértices del triángulo se
lean de un buffer de memoria en lugar de incluirlos en el
Vertex Shader.
|
|
La estructura
VkPipelineVertexInputStateCreateInfo
|
|
Los puntos de entrada de la información en el pipeline de
renderizado corresponden a las variables de entrada del Vertex
Shader, que corresponden a los atributos de los vértices, y las
variables uniformes definidas en cada shader.
Para describir la forma de acceder a los atributos de los vértices se
utiliza la estructura VkPipelineVertexInputStateCreateInfo
que forma parte de la información necesaria para definir un pipeline
gráfico.
typedef struct VkPipelineVertexInputStateCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineVertexInputStateCreateFlags flags;
uint32_t vertexBindingDescriptionCount;
const VkVertexInputBindingDescription* pVertexBindingDescriptions;
uint32_t vertexAttributeDescriptionCount;
const VkVertexInputAttributeDescription* pVertexAttributeDescriptions;
} VkPipelineVertexInputStateCreateInfo;
|
El campo sType es
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO.
Los campos pNext y flags están reservados para
futuras ampliaciones y deben dejarse a nulo.
Los valores de los atributos de los vértices se almacenan en
buffers, es decir, en bloques de memoria de la tarjeta gráfica.
El campo vertexBindingDescriptionCount indica el número de
buffers de los que se toman las entradas y el campo
pVertexBindingDescriptions indica la forma de acceder a esos
buffers.
El campo vertexAttributeDescriptionCount indica el número
de atributos que se van a leer en el Vertex Shader y el
campo pVertexAttributeDescriptions describe la forma de
leer esos atributos a partir de los buffers.
A continuación se muestra la definición de la estructura VkVertexInputBindingDescription
que describe la forma de acceder a un buffer de
almacenamiento de valores de los atributos.
typedef struct VkVertexInputBindingDescription {
uint32_t binding;
uint32_t stride;
VkVertexInputRate inputRate;
} VkVertexInputBindingDescription;
|
El campo binding se refiere al índice utilizado para
referenciar al buffer. Por ejemplo, si los atributos se encuentran
distribuidos entre tres buffers entonces es necesario definir tres
estructuras con los bindings 0, 1 y 2.
El campo stride indica la cantidad de bytes que se leerán
del buffer en cada ejecución del Vertex Shader.
Por ejemplo, si el buffer almacena para cada vértice una estructura
con un valor de tipo vec3 (12 bytes) y otro valor de tipo
vec2 (8 bytes) el valor del campo stride sería 20.
El campo inputRate indica la frecuencia con la que se lee
el buffer desde el Vertex Shader. Generalmente se
utiliza el valor VK_VERTEX_INPUT_RATE_VERTEX, que indica que
hay que leer los valores del buffer para cada vértice. También se
puede usar el valor VK_VERTEX_INPUT_RATE_INSTANCE, que indica que se
leerá del buffer para cada instancia de dibujo.
La descripción de los atributos se introduce mediante la estructura
VkVertexInputAttributeDescription que se define a
continuación.
typedef struct VkVertexInputAttributeDescription {
uint32_t location;
uint32_t binding;
VkFormat format;
uint32_t offset;
} VkVertexInputAttributeDescription;
|
El campo location indica el índice del atributo a
describir. Este valor se puede fijar en el código del shader
mediante el modificador layout. Por ejemplo, para fijar el
índice 0 en un atributo se indicaría "layout(location = 0)"
antes de la definición de la variable de entrada.
El campo binding indica el índice del buffer en el que se encuentra
almacenado el valor del atributo.
El campo format indica el formato en el que está almacenado el valor
del atributo en el buffer. Debe ser un valor de la enumeración
VkFormat. Por ejemplo, un valor de tipo vec3 se
almacena como un formato de tres valores en coma flotante de 32 bits
lo que corresponde al valor VK_FORMAT_R32G32B32_SFLOAT.
El campo offset se refiere a la posición en la que comienza
el dato en el bloque que se lee del buffer. Por ejemplo, si
el buffer almacena una estructura con un vec3 y un
vec2, el offset del primer atributo sería 0 y el
offset del segundo atributo sería el tamaño del vec3
(12 bytes).
|
|
Las estructuras VkBuffer y
VkDeviceMemory
|
|
Un buffer es una zona de memoria de la tarjeta gráfica en la que se almacenan
datos a utilizar en el proceso de renderizado. Para crear un buffer se
utiliza la función vkCreateBuffer().
VkResult vkCreateBuffer(
VkDevice device,
const VkBufferCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkBuffer* pBuffer);
|
La información necesaria para crear el buffer se
introduce en la estructura VkBufferCreateInfo.
typedef struct VkBufferCreateInfo {
VkStructureType sType;
const void* pNext;
VkBufferCreateFlags flags;
VkDeviceSize size;
VkBufferUsageFlags usage;
VkSharingMode sharingMode;
uint32_t queueFamilyIndexCount;
const uint32_t* pQueueFamilyIndices;
} VkBufferCreateInfo;
|
El campo sType debe ser
VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO.
El campo pNext suele dejarse a nulo, aunque se han definido
varias extensiones que permiten incuir información adicional.
El campo flags suele dejarse a nulo, pero pueden utilizarse
bits para indicar si el buffer utiliza memoria protegida o puede
tener asociada una memoria esparcida en el dispositivo.
El campo size se refiere al tamaño en bytes que requiere el buffer.
El campo usage indica el uso que tendrá el buffer, es decir, el tipo
de buffer a crear. Para los buffers utilizados para almacenar
atributos de vértices se utiliza el valor VK_BUFFER_USAGE_VERTEX_BUFFER_BIT.
Otros valores comunes son VK_BUFFER_USAGE_INDEX_BUFFER_BIT (para los buffers de índices
que utilizaremos en el proyecto 3.b) o VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
(para los buffers de variables uniformes vinculados a los descriptor
sets que utilizaremos en el proyecto 3.c).
El campo sharingMode indica si el buffer va a ser
compartido entre diferentes colas de comandos. Para indicar que el
buffer no se va a compartir se utiliza el valor
VK_SHARING_MODE_EXCLUSIVE. Si el buffer se va a compartir
se utiliza el valor VK_SHARING_MODE_CONCURRENT. En ese caso hay que
especificar cuales son las familias que pueden acceder al buffer de
forma concurrente mediante los campos queueFamilyIndexCount
(número de familias) y pQueueFamilyIndices (índices de las
familias).
El objeto VkBuffer contiene la descripción del buffer, pero para ubicarlo
en memoria es necesario crear una estructura paralela de tipo VkDeviceMemory,
que describe la memoria ocupada por el buffer. Para alojar la memoria se
utiliza la función vkAllocateMemory().
VkResult vkAllocateMemory(
VkDevice device,
const VkMemoryAllocateInfo* pAllocateInfo,
const VkAllocationCallbacks* pAllocator,
VkDeviceMemory* pMemory);
|
La estructura VkMemoryAllocateInfo describe el tamaño
a reservar y el tipo de memoria de entre los diferentes tipos
soportados por el dispositivo.
typedef struct VkMemoryAllocateInfo {
VkStructureType sType;
const void* pNext;
VkDeviceSize allocationSize;
uint32_t memoryTypeIndex;
} VkMemoryAllocateInfo;
|
El campo sType debe ser
El campo pNext debe ser nulo.
El campo allocationSize contiene el tamaño en bytes de la
memoria a alojar.
El campo memoryTypeIndex describe el tipo de memoria a
utilizar. Se refiere al índice del tipo entre las propiedades de
memoria del dispositivo. Para encontar ese índice hay que obtener
las propiedades de memoria del dispositivo por medio de la función
vkGetPhysicalDeviceMemoryProperties() y comparar los
diferentes tipos con las propiedades necesarias para el buffer, que
se pueden obtener con la función vkGetBufferMemoryRequirements().
Una vez reservada la memoria ya se pueden almacenar en ella los datos
del buffer. La función vkMapMemory() obtiene un puntero a la memoria
que se puede utilizar como referencia para copiar los datos.
A partir del puntero se pueden volcar los datos con la función memcpy().
Es importante liberar el puntero con vkUnmapMemory() porque mientras la
memoria está mapeada, la GPU no puede acceder a ella.
VkResult vkMapMemory(
VkDevice device,
VkDeviceMemory memory,
VkDeviceSize offset,
VkDeviceSize size,
VkMemoryMapFlags flags,
void** ppData);
void vkUnmapMemory(
VkDevice device,
VkDeviceMemory memory);
|
Los parámetros device y memory se refieren al
dispositivo y a la memoria a mapear. El parámetro offset
indica el punto en el que se comenzará a mapear la memoria. El
parámetro size es el tamaño en bytes a mapear. Se puede
utilizar el valor VK_WHOLE_SIZE para mapear hasta el final de la
memoria reservada. El parámetro flags está reservado para
un uso futuro. El puntero a la memoria se devuelve en el parámetro
ppData.
Una vez creado el buffer, reservada la memoria y copiados en ella
los datos, es necesario vincular el buffer a la memoria por medio
de la función vkBindBufferMemory().
VkResult vkBindBufferMemory(
VkDevice device,
VkBuffer buffer,
VkDeviceMemory memory,
VkDeviceSize memoryOffset);
|
|
|
Comandos
|
|
El paso final para utilizar los buffers como puntos de entrada del proceso
de renderizado es activarlos en el buffer de comandos antes de llamar al
comando Draw. Para eso se utiliza la función vkCmdBindVertexBuffers().
void vkCmdBindVertexBuffers(
VkCommandBuffer commandBuffer,
uint32_t firstBinding,
uint32_t bindingCount,
const VkBuffer* pBuffers,
const VkDeviceSize* pOffsets);
|
Como los atributos pueden estar almacenados en varios buffers, el
parámetro pBuffers contiene una lista de buffers. El parámetro
firstBinding indica cual es el primer binding a
vincular. El parámetro bindingCount indica el número de
bindings a vincular con los buffers. El parámetro
pOffsets define el desplazamiento inicial sobre cada buffer,
es decir, el byte en el que se comenzará a leer cada buffer.
En una escena a dibujar generalmente aparecen varios objetos cuyos
vértices estarán almacenados en buffers diferentes. A la
hora de hacer el dibujo completo hay que dibujar los objetos
secuencialmente. Para dibujar cada objeto hay que enlazar sus
VertexBuffers y seguidamente ejecutar el comando Draw.
|
|
Shaders
|
|
Para incluir los atributos de los vértices solo es necesario
modificar el Vertex Shader dejando inalterado el
Fragment Shader. En este caso se va a sustituir la declaración
de los atributos en el mismo código con la declaración de los
atributos como variables de entrada (in). Para asegurar la
posición asociada a cada atributo se ha utilizado el modificador
layout con la propiedad location. De esta forma se
garantiza que el atributo inPosition corresponde a la
posición 0 y que el atributo inColor corresponde a la
posición 1. El código de la función main() se ha modificado
para tomar el valor de esas variables de entrada.
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 0) out vec3 fragColor;
void main()
{
gl_Position = vec4(inPosition, 0.0, 1.0);
fragColor = inColor;
}
|
|
|
La estructura GEVertex
|
|
Para describir la información asociada a cada vértice se ha incluido
una nueva estructura llamada GEVertex. Esta estructura
corresponde al bloque de información asociada a cada vértice que se
almacenará en el buffer. En cada ejecución del Vertex
Shader se leerá de la memoria un bloque de este tipo y se
asignará a las variables de entrada del Vertex Shader. En
este caso la estructura está formada por dos campos que almacenarán
los valores de los atributos inPosition e inColor.
#pragma once
#include <glm\glm.hpp>
typedef struct
{
glm::vec2 pos;
glm::vec3 color;
} GEVertex;
|
|
|
La clase GEVertexBuffer
|
|
La clase GEVertexBuffer contine la información
necesaria para construir y gestionar un buffer con los valores de
los atributos de los vértices. Como hemos visto, esto requiere
construir un objeto VkBuffer para describir la estructura
del buffer y un objeto VkDeviceMemory para definir la zona
de memoria donde almacenar la información del buffer.
#pragma once
#include <vulkan/vulkan.h>
#include "GEGraphicsContext.h"
class GEVertexBuffer
{
public:
VkBuffer buffer;
VkDeviceMemory memory;
GEVertexBuffer(GEGraphicsContext* gc, size_t size, const void* data);
void destroy(GEGraphicsContext* gc);
};
|
A continuación se muestra el contenido de los métodos de laa clase GEVertexBuffer.
#include "GEVertexBuffer.h"
#include <iostream>
//
// FUNCIÓN: GEVertexBuffer::GEVertexBuffer()
//
// PROPÓSITO: Crea un Vertex Buffer
//
GEVertexBuffer::GEVertexBuffer(GEGraphicsContext* gc, size_t size, const void* data)
{
VkBufferCreateInfo bufferInfo = {};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = size;
bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (vkCreateBuffer(gc->device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS)
{
throw std::runtime_error("failed to create buffer!");
}
VkMemoryRequirements memRequirements;
vkGetBufferMemoryRequirements(gc->device, buffer, &memRequirements);
VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = gc->findMemoryType(memRequirements.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
if (vkAllocateMemory(gc->device, &allocInfo, nullptr, &memory) != VK_SUCCESS)
{
throw std::runtime_error("failed to allocate buffer memory!");
}
vkBindBufferMemory(gc->device, buffer, memory, 0);
void* gpudata;
vkMapMemory(gc->device, memory, 0, size, 0, &gpudata);
memcpy(gpudata, data, size);
vkUnmapMemory(gc->device, memory);
}
//
// FUNCIÓN: GEVertexBuffer::destroy(GEGraphicsContext* gc)
//
// PROPÓSITO: Destruye los campos de un Vertex Buffer
//
void GEVertexBuffer::destroy(GEGraphicsContext* gc)
{
vkDestroyBuffer(gc->device, buffer, nullptr);
vkFreeMemory(gc->device, memory, nullptr);
}
|
|
|
La clase GEFigure
|
|
La clase GEFigure contiene la información y las funciones
necesarias para representar la figura a dibujar, en este caso el
triángulo. Los campos de la clase incluyen las referencias al objeto
GEVertexBuffer en el que se almacenan los atributos de los vértices. Los métodos de la clase permiten
inicializar y finalizar el uso de la figura, es decir, crear y destruir los buffers
que la forman, y añadir los comandos de
dibujo a un determinado buffer de comandos. La constante
vertices contiene los datos que se almacenarán en el vertex
buffer.
#pragma once
#include "GEGraphicsContext.h"
#include "GEVertex.h"
#include "GEVertexBuffer.h"
#include <vector>
const std::vector<GEVertex> vertices =
{
{{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}},
{{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}},
{{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}
};
class GEFigure
{
public:
GEFigure(GEGraphicsContext* gc);
void destroy(GEGraphicsContext* gc);
void addCommands(VkCommandBuffer commandBuffer, int index);
private:
GEVertexBuffer* vbo;
};
|
A
continuación se muestra el código de la clase. El constructor es el encargado de crear el vertex buffer.
Para ello utiliza los datos de la constante vertices incluida en el
fichero de cabecera de la clase. El método destroy()
destruye el vertex buffer. El método addCommands() recibe un buffer de
comandos y le añade los comandos vkCmdBindVertexBuffers y
vkCmdDraw para lanzar el proceso de dibujo de la figura.
#include "GEFigure.h"
#include "GEVertex.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <iostream>
//
// FUNCIÓN: GEFigure::GEFigure(GEGraphicsContext* gc)
//
// PROPÓSITO: Crea el Vertex Buffer
//
GEFigure::GEFigure(GEGraphicsContext* gc)
{
size_t vertexSize = sizeof(GEVertex) * vertices.size();
vbo = new GEVertexBuffer(gc, vertexSize, vertices.data());
}
//
// FUNCIÓN: GEFigure::destroy(GEGraphicsContext* gc)
//
// PROPÓSITO: Libera los buffers de la figura
//
void GEFigure::destroy(GEGraphicsContext* gc)
{
vbo->destroy(gc);
delete vbo;
}
//
// FUNCIÓN: CAFigure::addCommands(VkCommandBuffer commandBuffer, int index)
//
// PROPÓSITO: Añade los comandos de renderizado al command buffer
//
void GEFigure::addCommands(VkCommandBuffer commandBuffer, int index)
{
VkDeviceSize offset = 0;
vkCmdBindVertexBuffers(commandBuffer, 0, 1, &(vbo->buffer), &offset);
vkCmdDraw(commandBuffer, (uint32_t) vertices.size(), 1, 0, 0);
}
|
|
|
La clase GEScene
|
|
La clase GEScene se ha añadido al proyecto para almacenar las referencias a las figuras que
forman parte del modelo 3D a representar. Esta clase contiene el
objeto GERenderingContext con la configuración del proceso
de renderizado (renderpass, pipeline y
framebuffers), que se ha trasladado desde la versión anterior
de la clase GEApplication, así como el
campo figure que contiene la descripción del triángulo que
vamos a dibujar.
#pragma once
#include "GEGraphicsContext.h"
#include "GEDrawingContext.h"
#include "GECommandContext.h"
#include "GERenderingContext.h"
#include "GEFigure.h"
#include <vulkan/vulkan.h>
#include <vector>
//
// CLASE: GEScene
//
// DESCRIPCIÓN: Clase que describe una escena
//
class GEScene
{
private:
GERenderingContext* rc;
GEFigure* figure;
public:
GEScene(GEGraphicsContext* gc, GEDrawingContext* dc, GECommandContext* cc);
void destroy(GEGraphicsContext* gc);
void recreate(GEGraphicsContext* gc, GEDrawingContext* dc, GECommandContext* cc);
private:
void fillCommandBuffers(std::vector commandBuffers);
GEPipelineConfig* createPipelineConfig(VkExtent2D extent);
};
|
Los métodos de la clase son los siguientes:
-
GEScene(): Constructor de la clase. Es
el responsable de crear el conterxto de renderizado, crear la
figura que contiene la escena y rellenar los buffers de
comandos.
-
destroy(): Destruye los componentes de
la clase.
-
recreate(): Reconstruye los
componentes ante un cambio de tamaño de la ventana dela
aplicación.
-
fillCommandBuffers(): Rellena los
buffers de comandos.
-
createPipelineConfig(): Crea la
configuración del pipeline de renderizado. En este caso se ha
modificado la descripción de los atributos de los vértices para
indicar que se van a utilizar dos atributos (pos y color) de
tipo vec2 y vec3 respectivamente.
El código de
los métodos es el siguiente.
#include "GEScene.h"
#include <windows.h>
#include "resource.h"
//
// FUNCIÓN: GEScene::GEScene(GEGraphicsContext* gc, GEDrawingContext* dc)
//
// PROPÓSITO: Crea la escena
//
GEScene::GEScene(GEGraphicsContext* gc, GEDrawingContext* dc, GECommandContext* cc)
{
GEPipelineConfig* config = createPipelineConfig(dc->getExtent());
this->rc = new GERenderingContext(gc, dc, config);
this->figure = new GEFigure(gc);
fillCommandBuffers(cc->commandBuffers);
}
//
// FUNCIÓN: GEScene::destroy(GEGraphicsContext* gc)
//
// PROPÓSITO: Reconstruye los componentes gráficos de la escena
//
void GEScene::destroy(GEGraphicsContext* gc)
{
rc->destroy(gc);
figure->destroy(gc);
delete rc;
delete figure;
}
//
// FUNCIÓN: GEScene::recreate(GEGraphicsContext* gc, GEDrawingContext* dc)
//
// PROPÓSITO: Reconstruye los componentes gráficos de la escena
//
void GEScene::recreate(GEGraphicsContext* gc,
GEDrawingContext* dc,
GECommandContext* cc)
{
rc->destroy(gc);
GEPipelineConfig* config = createPipelineConfig(dc->getExtent());
this->rc = new GERenderingContext(gc, dc, config);
fillCommandBuffers(cc->commandBuffers);
}
//
// FUNCIÓN: CAScene::addCommands(VkCommandBuffer commandBuffer, int index)
//
// PROPÓSITO: Añade los comandos de renderizado al command buffer
//
void GEScene::addCommands(VkCommandBuffer commandBuffer, int index)
{
figure->addCommands(commandBuffer, index);
}
//
// FUNCIÓN: GEScene::createPipelineConfig()
//
// PROPÓSITO: Obtiene la configuración del pipeline de renderizado
//
GEPipelineConfig* GEScene::createPipelineConfig(VkExtent2D extent)
{
GEPipelineConfig* config = new GEPipelineConfig();
config->vertex_shader = IDR_HTML1;
config->fragment_shader = IDR_HTML2;
config->attrStride = sizeof(GEVertex);
config->attrOffsets.resize(2);
config->attrOffsets[0] = offsetof(GEVertex, pos);
config->attrOffsets[1] = offsetof(GEVertex, color);
config->attrFormats.resize(2);
config->attrFormats[0] = VK_FORMAT_R32G32_SFLOAT;
config->attrFormats[1] = VK_FORMAT_R32G32B32_SFLOAT;
config->descriptorTypes.resize(0);
config->descriptorStages.resize(0);
config->depthTestEnable = VK_TRUE;
config->cullMode = VK_CULL_MODE_NONE;
config->extent = extent;
return config;
}
|
|
|
Modificaciones de las clases
GEApplication y GERenderingContext
|
|
Para finalizar el proyecto se han realizado pequeñas modificaciones
en las clases GEApplication y GERenderingContext. En la clase
GEApplication se ha eliminado la referencia al objeto
GERenderingContext y se ha añadido la referencia al objeto
GEScene.
También se ha eliminado el método createPipelineConfig() ya que la
generación del contexto de renderizado se ha trasladado a la clase
GEScene.
//
// 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());
this->scene = new GEScene(gc, dc, cc);
mainLoop();
cleanup();
}
//
// FUNCIÓN: GEApplication::cleanup()
//
// PROPÓSITO: Libera los recursos y finaliza la aplicación
//
void GEApplication::cleanup()
{
scene->destroy(gc);
cc->destroy(gc);
dc->destroy(gc);
delete scene;
delete cc;
delete dc;
delete gc;
glfwDestroyWindow(window);
glfwTerminate();
}
//
// FUNCIÓN: GEApplication::resize(GLFWwindow* window, int width, int height)
//
// PROPÓSITO: Reconstruye los objetos con el nuevo tamaño de ventana
//
void GEApplication::resize()
{
if (!windowPos.fullScreen)
{
glfwGetWindowSize(window, &windowPos.width, &windowPos.height);
glfwGetWindowPos(window, &windowPos.Xpos, &windowPos.Ypos);
}
dc->recreate(gc, windowPos);
cc->destroy(gc);
delete cc;
this->cc = new GECommandContext(this->gc, this->dc->getImageCount());
scene->recreate(gc, dc, cc);
}
|
Respecto a la clase GERenderingContext el cambio ha
consistido en modificar el método fillCommandBuffers() para
dividirlo en dos métodos: startFillingCommandBuffers() y
endFillingCommandBuffers().
//
// FUNCIÓN: GERenderingContext::startFillingCommandBuffers()
//
// PROPÓSITO: Actualiza los buffers de comandos para añadir el renderizado
//
void GERenderingContext::startFillingCommandBuffers(
std::vector<VkCommandBuffer> commandBuffers)
{
for (size_t i = 0; i < commandBuffers.size(); i++)
{
VkCommandBufferBeginInfo beginInfo = {};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS)
{
throw std::runtime_error("failed to begin recording command buffer!");
}
VkClearValue clearValues[2];
clearValues[0].color = { 1.0f, 1.0f, 1.0f, 1.0f };
clearValues[1].depthStencil = { 1.0f, 0 };
VkRenderPassBeginInfo renderPassInfo = {};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = renderPass;
renderPassInfo.framebuffer = framebuffers[i];
renderPassInfo.renderArea.offset = { 0, 0 };
renderPassInfo.renderArea.extent = extent;
renderPassInfo.clearValueCount = 2;
renderPassInfo.pClearValues = clearValues;
vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo,
VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS,
graphicsPipeline);
scene->addCommands(commandBuffers[i], i);
vkCmdEndRenderPass(commandBuffers[i]);
if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS)
{
throw std::runtime_error("failed to record command buffer!");
}
}
}
//
// FUNCIÓN: GERenderingContext::endFillingCommandBuffers()
//
// PROPÓSITO: Actualiza los buffers de comandos para añadir el renderizado
//
void GERenderingContext::endFillingCommandBuffers(
std::vector<VkCommandBuffer> commandBuffers)
{
for (size_t i = 0; i < commandBuffers.size(); i++)
{
vkCmdEndRenderPass(commandBuffers[i]);
if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS)
{
throw std::runtime_error("failed to record command buffer!");
}
}
}
|
|
|
Aspecto final
|
|
El aspecto final es el mismo que el del proyecto Project3e ya que
solo se ha modificado la forma en la que se leen los valores de los
atributos del triángulo.
|
|