|
Grado en Ingeniería Informática
Animación por Ordenador
Curso 2025/2026
|
Práctica 3c
|
|
Creación del pipeline de renderizado
|
|
Objetivos
|
|
Completa la creación del pipeline de renderizado incluyendo
los shaders y las opciones configurables.
|
|
La configuración del Pipeline
de renderizado
|
|
Para describir un proceso de renderizado en Vulkan es necesario crear
un objeto VkPipeline por medio de la función
vkCreateGraphicsPipelines(). Realmente esta función permite
crear un conjunto de objetos VkPipeline. El parámetro
createInfoCount indica el número de objetos a crear y el
campo pCreateInfos contiene la información para crear cada
objeto.
VkResult vkCreateGraphicsPipelines (
VkDevice device,
VkPipelineCache pipelineCache,
uint32_t createInfoCount,
const VkGraphicsPipelineCreateInfo* pCreateInfos,
const VkAllocationCallbacks* pAllocator,
VkPipeline* pPipelines);
|
Para destruir un objeto
VkPipeline se usa la función vkDestroyPipeline().
void vkDestroyPipeline (
VkDevice device,
VkPipeline pipeline,
const VkAllocationCallbacks* pAllocator);
|
La configuración del pipeline de renderizado se describe en
estructuras VkGraphicsPipelineCreateInfo. A continuación se
muestra la definición de esta estructura que, como puede apreciarse,
está formada por numerosos campos.
typedef struct VkGraphicsPipelineCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineCreateFlags flags;
uint32_t stageCount;
const VkPipelineShaderStageCreateInfo* pStages;
const VkPipelineVertexInputStateCreateInfo* pVertexInputState;
const VkPipelineInputAssemblyStateCreateInfo* pInputAssemblyState;
const VkPipelineTessellationStateCreateInfo* pTessellationState;
const VkPipelineViewportStateCreateInfo* pViewportState;
const VkPipelineRasterizationStateCreateInfo* pRasterizationState;
const VkPipelineMultisampleStateCreateInfo* pMultisampleState;
const VkPipelineDepthStencilStateCreateInfo* pDepthStencilState;
const VkPipelineColorBlendStateCreateInfo* pColorBlendState;
const VkPipelineDynamicStateCreateInfo* pDynamicState;
VkPipelineLayout layout;
VkRenderPass renderPass;
uint32_t subpass;
VkPipeline basePipelineHandle;
int32_t basePipelineIndex;
} VkGraphicsPipelineCreateInfo;
|
El campo sType debe tener el valor
VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO.
El campo pNext suele dejarse nulo, aunque existen
extensiones de algunos fabricantes que requieren información extra
que puede introducirse en este campo.
El campo flags puede ser nulo o alguno de los siguientes
valores (las extensiones han añadido muchos otros posibles valores,
por ejemplo para incorporar etapas de ray tracing) :
-
VK_PIPELINE_CREATE_DISABLE_OPTIMIZATION_BIT: indica que no es
necesario optimizar el pipeline, de manera que se acelere la
creación aunque el resultado sea un pipeline más lento en
ejecución.
-
VK_PIPELINE_CREATE_ALLOW_DERIVATIVES_BIT: indica que se pueden
crear nuevos pipelines derivados de este, de manera que el
intercambio entre ellos pueda hacerse más rápido.
-
VK_PIPELINE_CREATE_DERIVATIVE_BIT: indica que el pipeline a
crear es un derivado de un pipeline anterior.
El campo stageCount contiene el número de shaders incluidos
en el pipeline y que corresponde al tamaño del array pStages.
El campo pStages contiene el conjunto de estructuras
VkPipelineShaderStageCreateInfo que definen los shaders
incluidos en el pipeline. El contenido de esta estructura y la forma
de definir los shaders se describieron en el proyecto anterior.
El campo pVertexInputState describe los atributos de
entrada utilizados en el pipeline. Estos atributos corresponden a
las variables de entrada descritas en el Vertex Shader. En esta
práctica no se van a utilizar atributos almacenados en memoria por
lo que la descripción en este caso será muy simple.
El campo pInputAssemblyState indica la forma de las
primitivas a ensamblar (puntos, líneas, triángulos o patches).
El campo pTessellationState describe el número de
puntos que forman un patch cuando el pipeline incluye teselado.
El campo pViewportState define las dimensiones del
trozo de imagen a generar. Normalmente coincide con el tamaño
completo de la imagen, aunque puede configurarse un pipeline que
solo genere un trozo. También incluye información sobre el recorte a
mostrar (scissor), que será el trozo del viewport
que se copiará en la imagen.
El campo pRasterizationState describe detalles del proceso
de rasterización previo al Fragment Shader. Aquí se incluyen
aspectos como el modo de dibujo (puntos, arista, relleno), la forma
de distinguir la cara frontal de la posterior en los triángulos, el
grosor de las líneas, …
El campo pMultisampleState define la forma en que se va
ejecutar el Fragment Shader sobre cada fragmento. Por defecto el
color del fragmento se calcula con la posición del centro del pixel,
pero se pueden configurar procesos de multisampleado en los que el
color del fragmento es un promedio del cálculo realizado sobre
varias posiciones.
El campo pDepthStencilState describe el test de profundidad
(depth) y el test de plantilla (stencil). El test de profundidad
permite saber si un píxel a dibujar ha sido dibujado previamente
como parte de un objeto que está más cerca. El test de plantilla
permite realizar comparaciones con otra información de estos
píxeles.
El campo pColorBlendState indica la forma de mezclar
colores entre fragmentos con niveles de transparencia.
El campo pDynamicState permite indicar que algunos de los
aspectos configurados por las estructuras anteriores van a ser
dinámicos y pueden ser modificados por comandos sin necesidad de
tener que crear un pipeline diferente.
El campo layout permite describir los Descriptor Sets
y las Push Constants del proceso de renderizado. Estos
objetos se utilizan para asignar los valores de las variables
uniformes definidas en los shaders. Por ejemplo, las matrices de
transformación espacial, la información sobre el modelo de
iluminación o las texturas son valores que se suelen introducir en
el pipeline mediante este tipo de objetos.
El campo renderPass contiene una estructura de tipo
VkRenderPass donde se describe la forma en la que la imagen a
generar se va a almacenar. La imagen final se puede generar en
varias pasadas, que se pueden configurar como “subpasses” de un
mismo renderPass.
El campo subpass indica el índice de la subpasada en el que
se va a ejecutar el pipeline (que será 0 si solo hay una pasada).
El campo basePipelineHandle indica cual es el pipeline
padre si estamos creando un pipeline derivado. En caso contrario
debe dejarse a nulo.
El campo basePipelineIndex es el índice que ocupa el
pipeline padre en la lista a generar por el método vkCreateGraphicsPipelines()
si estamos creando un pipeline derivado. En caso contrario se deja a
cero.
|
|
La estructura
VkPipelineVertexInputStateCreateInfo
|
|
La estructura VkPipelineVertexInputStateCreateInfo describe
la forma en la que se accede a los atributos de los vértices.
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 debe ser
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO.
El campo pNext por el momento debe dejarse a nulo.
El campo flags está reservado para un uso futuro y por el
momento se deja a cero.
El campo vertexBindingDescriptionCount contiene el número
de estructuras descritas en el siguiente campo.
El campo pVertexBindingDescriptions es un array de
estructuras que describen la forma de acceder a los atributos.
El campo vertexAttributeDescriptionCount contiene el número
de atributos descritos en el siguiente campo.
El campo pVertexAttributeDescriptions define el tipo de
dato correspondiente al atributo, la posición que ocupa en el Vertex
Shader y la forma de acceder a él.
|
|
La estructura
VkPipelineInputAssemblyStateCreateInfo
|
|
La estructura VkPipelineInputAssemblyStateCreateInfo
permite describir la forma de las primitivas a dibujar.
typedef struct VkPipelineInputAssemblyStateCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineInputAssemblyStateCreateFlags flags;
VkPrimitiveTopology topology;
VkBool32 primitiveRestartEnable;
} VkPipelineInputAssemblyStateCreateInfo;
|
El campo sType debe ser
VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO.
El campo pNext debe dejarse a nulo.
El campo flags debe dejarse a cero.
El campo topology describe la topología, es decir, la forma
de las primitivas a dibujar. Debe ser uno de los valores de la
enumeración VkPrimitiveTopology.
El campo primitiveRestartEnable se utiliza para
trocear las listas. Generalmente se deja a falso, pero si se
activa indica que en los dibujos indexados el índice -1 marca el
final de una lista.
Las topologías que se admiten en Vulkan son las siguientes (con el
prefijo VK_PRIMITIVE_TOPOLOGY_):


|
|
La estructura
VkPipelineTessellationStateCreateInfo
|
|
La estructura VkPipelineTessellationStateCreateInfo
indica la configuración del proceso de teselado.
typedef struct VkPipelineTessellationStateCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineTessellationStateCreateFlags flags;
uint32_t patchControlPoints;
} VkPipelineTessellationStateCreateInfo;
|
El campo sType debe ser
VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO.
El campo pNext debe dejarse a nulo.
El campo flags debe dejarse a cero.
El único campo a completar es patchControlPoints, que
indica el número de puntos que forman un PATCH.
|
|
La estructura VkPipelineViewportStateCreateInfo
|
|
La estructura VkPipelineViewportStateCreateInfo
describe el viewport a utilizar. El viewport se refiere al tamaño
del volumen de recorte en coordenadas de píxel. En coordenadas de
recorte (o coordenadas clip) el volumen de recorte se
describe con valores entre -1 y +1 en los tres ejes. La
transformación a coordenadas viewport permite calcular en
qué pixel se proyecta cada vértice. Normalmente el tamaño del
viewport coincide con el tamaño de la ventana sobre la que
queremos dibujar la imagen. El estado de viewport permite también
definir el trozo de imagen que queremos que se vuelque en la ventana.
Lo habitual es que coincida también con el tamaño de la ventana.
typedef struct VkPipelineViewportStateCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineViewportStateCreateFlags flags;
uint32_t viewportCount;
const VkViewport* pViewports;
uint32_t scissorCount;
const VkRect2D* pScissors;
} VkPipelineViewportStateCreateInfo;
|
El campo sType debe ser
VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO.
El campo pNext debe dejarse a nulo.
El campo flags debe dejarse a cero.
El campo
viewportCount indica el número de viewports a
configurar en el pipeline. Normalmente es solo uno.
El campo pViewports es un array de estructuras VkViewport.
Estas estructuras indican los valores de los extremos del volumen de
recorte. La esquina superior izquierda corresponde a los valores (x,y)
y la esquina inferior derecha a los valores (x+width,y+height).
Los valores extremos de la componente Z son minDepth y
maxDepth.
typedef struct VkViewport {
float x;
float y;
float width;
float height;
float minDepth;
float maxDepth;
} VkViewport;
|
El campo scissorCount indica el tamaño del campo
posterior, es decir, el número de scissors definidos.
El campo pScissors describe el array de trozos de
imagen a rellenar asociado a cada viewport.
typedef struct VkRect2D {
VkOffset2D offset;
VkExtent2D extent;
} VkRect2D;
typedef struct VkOffset2D {
int32_t x;
int32_t y;
} VkOffset2D;
typedef struct VkExtent2D {
uint32_t width;
uint32_t height;
} VkExtent2D;
|
La siguiente imagen muestra la transformación entre coordenadas clip
y coordenadas viewport.

|
|
La estructura
VkPipelineRasterizationStateCreateInfo
|
|
La estructura VkPipelineRasterizationStateCreateInfo
configura la etapa de rasterización, es decir, la forma en la que se
tratan las primitivas para decidir qué fragmentos se dibujan.
typedef struct VkPipelineRasterizationStateCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineRasterizationStateCreateFlags flags;
VkBool32 depthClampEnable;
VkBool32 rasterizerDiscardEnable;
VkPolygonMode polygonMode;
VkCullModeFlags cullMode;
VkFrontFace frontFace;
VkBool32 depthBiasEnable;
float depthBiasConstantFactor;
float depthBiasClamp;
float depthBiasSlopeFactor;
float lineWidth;
} VkPipelineRasterizationStateCreateInfo;
|
El campo sType debe ser
VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO.
El campo pNext debe dejarse a nulo.
El campo flags debe dejarse a cero.
El campo depthClampEnable indica el comportamiento con aquellos
puntos que tengan una profundidad mayor que el límite del clipping
volume. Si este campo es VK_TRUE los puntos de profundidad mayor al
límite se dibujan considerando que su profundidad es la máxima. Si
el campo es VK_FALSE estos puntos no se dibujan.
El campo rasterizerDiscardEnable se activa si se
quiere descartar todo el proceso de rasterización. En ese caso el
pipeline solo se ejecutará hasta el comienzo de esta etapa y nunca
llegará a generarse la imagen. Esto se utiliza cuando se desea
almacenar el resultado de las etapas anteriores en memoria para
tratarlos posteriormente (posiblemente en un subpase posterior).
El campo polygonMode indica la forma en que se dibujarán las
primitivas de tipo triángulo: en modo relleno (VK_POLYGON_MODE_FILL), en modo arista (VK_POLYGON_MODE_LINE)
o si se dibujarán solo los vértices (VK_POLYGON_MODE_POINT).
El campo cullMode define si se quiere eliminar del
proceso de creación de la imagen a aquellos triángulos dibujados por
la cara posterior (VK_CULL_MODE_BACK_BIT), por la cara frontal (VK_CULL_MODE_FRONT_BIT), por cualquiera de las
caras (VK_CULL_MODE_FRONT_AND_BACK) o no eliminar ninguna (VK_CULL_MODE_NONE).
El campo frontFace define si las caras frontales son las
que se dibujan en el sentido horario (VK_FRONT_FACE_CLOCKWISE) o antihorario (VK_FRONT_FACE_COUNTER_CLOCKWISE).
Los campos depthBiasEnable, depthBiasConstantFactor,
depthBiasClamp y depthBiasSlopeFactor permiten
definir un desplazamiento sobre la profundidad de los puntos.
El campo lineWidth define el grosor de las líneas medido en
pixels. Este campo afecta a las primitivas de tipo línea o a los
triángulos dibujados en modo arista.
|
|
La estructura
VkPipelineMultisampleStateCreateInfo
|
|
La estructura VkPipelineMultisampleStateCreateInfo describe la forma
en la que se va a samplear cada fragmento (píxel), es decir, la forma
en la que se ejecutará el Fragment Shader sobre cada pixel.
typedef struct VkPipelineMultisampleStateCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineMultisampleStateCreateFlags flags;
VkSampleCountFlagBits rasterizationSamples;
VkBool32 sampleShadingEnable;
float minSampleShading;
const VkSampleMask* pSampleMask;
VkBool32 alphaToCoverageEnable;
VkBool32 alphaToOneEnable;
} VkPipelineMultisampleStateCreateInfo;
|
Existen
dos formas de hacerlo: multisampleado y supersampleado.
El multisampleado consiste en estudiar varias posiciones dentro del pixel para
ver si pertenecen o no a la primitiva, pero solo se ejecuta el Fragment Shader
sobre una posición. El supersampleado ejecuta el Fragment Shader sobre varias
posiciones del pixel para obtener un color promedio.

El campo sType debe ser
VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO.
El campo pNext debe dejarse a nulo.
El campo flags debe dejarse a cero.
El campo rasterizationSamples define el número de
puntos que se van a estudiar para cada pixel. La siguiente
imagen muestra los valores admitidos y las posiciones que se
analizan en cada caso.

El campo sampleShadingEnable permite elegir
entre multisampleado (VK_FALSE) y supersampleado (VK_TRUE)..
El supersampleado no obliga a evaluar el Fragment Shader sobre
todos los puntos, sino sobre un porcentaje mínimo que se define en
el campo minSampleShading .
El campo pSampleMask se utiliza para indicar cuales
de los valores generados por el Fragment Shader al hacer
supersampleado se van a almacenar en el framebuffer.
El campo alphaToCoverageEnable permite activar un modo de sampleado en el que
en la primera ejecución del Fragment Shader se almacena en el canal alpha un
valor de covertura específico de cada pixel (como un minSampleShading
generado para cada pixel).
Si el campo anterior está activo el resultado final del canal alpha se puede calcular
como el resto de componentes o como el valor uno si se activa el campo
alphaToOneEnable.
|
|
La estructura VkPipelineDepthStencilStateCreateInfo
|
|
Una vez decidido que un fragmento forma parte de una primitiva
y debe ser dibujado es posible que previamente ya se hubiera
calculado el color de esa misma posición. Esto ocurre cuando dos
primitivas se solapan al proyectarse lo que sucede cuando cuando se
dibujan varios objetos y uno está más cerca que otro. Este solape se
puede producir también entre primitivas de un mismo objeto. En ese
caso hay que decidir si se dibuja el nuevo fragmento o no en función
de la profundidad a la que se encuentre el punto que se desea dibujar
y la profundidad a la que se encontrara el punto ya dibujado. Es
decir, la decisión de si el nuevo fragmento se dibuja o no se toma en
función de un test de profundidad.
El funcionamiento del pipeline permite configurar este test de
profundidad y otro test, denominado stencil, que en vez de
la profundidad (que se almacena automáticamente) utiliza los datos
de otro buffer (que hay que asignar explícitamente). La estructura
VkPipelineDepthStencilStateCreateInfo define la
forma en la que se realizan los test de profundidad (depth)
y de plantilla (stencil) para decidir que un fragmento debe
ser dibujado.
typedef struct VkPipelineDepthStencilStateCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineDepthStencilStateCreateFlags flags;
VkBool32 depthTestEnable;
VkBool32 depthWriteEnable;
VkCompareOp depthCompareOp;
VkBool32 depthBoundsTestEnable;
VkBool32 stencilTestEnable;
VkStencilOpState front;
VkStencilOpState back;
float minDepthBounds;
float maxDepthBounds;
} VkPipelineDepthStencilStateCreateInfo;
|
El campo sType debe ser
VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_CREATE_INFO.
El campo pNext debe dejarse a nulo.
El campo flags debe dejarse a cero.
El campo depthTestEnable permite activar el
test de profundidad. Si el test no se activa el nuevo fragmento se
dibujará siempre, machacando cualquier información almacenada
anteriormente.
El campo
depthWriteEnable indica si el valor de la profundidad debe
almacenarse. Lo normal es que sí.
La operación de comparación a utilizar se especifica mediante el campo
depthCompareOp. Suele utilizarse VK_COMPARE_OP_LESS o VK_COMPARE_OP_LESS_OR_EQUAL
aunque se admite cualquier valor de tipo VkCompareOp.
Otro posibilidad es comparar la profundidad con un rango. Para activar
esa opción se utiliza el campo depthBoundsTestEnable y se utilizan los
campos minDepthBounds y maxDepthBounds para indicar el rango.
El test de plantilla se realiza después del test de profundidad, activando
el campo stencilTestEnable. Se puede configurar de forma distinta este test
para los pixeles mostrados en una cara frontal (campo front) o en una cara
posterior (campo back). La estuctura VkStencilOpState permite configurar
tanto la operación de comparación como la acción a realizar si el test es
positivo o negativo.
|
|
La estructura VkPipelineColorBlendStateCreateInfo
|
|
Una vez ejecutado el Fragment Shader sobre un pixel, que a su vez ha
superado anteriormente el test de profundidad y de plantilla, se puede
escribir la información del pixel sobre la imagen. Pero en ese momento
existe un valor del pixel actual (source color) y un valor del pixel anterior
(destination color). En este
punto se puede decidir mezclar ambos colores para tener en cuenta la
transparencia. Esto se configura por medio de la estructura
VkPipelineColorBlendStateCreateInfo.
typedef struct VkPipelineColorBlendStateCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineColorBlendStateCreateFlags flags;
VkBool32 logicOpEnable;
VkLogicOp logicOp;
uint32_t attachmentCount;
const VkPipelineColorBlendAttachmentState* pAttachments;
float blendConstants[4];
} VkPipelineColorBlendStateCreateInfo;
|
El campo sType debe ser
VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO.
El campo pNext debe dejarse a nulo.
El campo flags debe dejarse a cero.
El campo logicOpEnable permite activar una opción de
mezclado basado en una operación lógica simple entre los colores.
El campo
logicOp indica la operación a realizar si el campo anterior
está activo. Se trata de operaciones a nivel de bit entre los
valores del source color y el destination color. Por ejemplo,
VK_LOGIC_OP_AND realiza una operación AND a nivel de bit entre los
dos valores.
Normalmente la forma de realizar una mezcla no es por medio de una
operación simple sino una combinación de los colores por medio de
una función más elaborada. Estas operaciones se definen por medio de
la estructura VkPipelineColorBlendAttachmentState. El campo
attachmentCount indica el número de estructuras
incluidas en el array pAttachments.
El campo pAttachments describe el proceso de mezcla por
medio de estructuras de tipo
VkPipelineColorBlendAttachmentState. Si el framebuffer sobre el
que se trabaja tiene varios buffers de color entonces cada estrutura
se refiere a la configuración de mezcla en cada buffer.
El campo blendConstants incluye constantes a utilizar en el
proceso de mezcla. Estas constantes se utilizan en algunos factores
de mezcla que se pueden utilizar en los pAttachments.
La estructura VkPipelineColorBlendStateCreateInfo se utiliza
para definir operaciones de mezcla que utilizan factores para
combinar los valores de los colores a mezclar. La estructura define
de forma separada la combinación de las componentes RGB y de la
componente alpha.
typedef struct VkPipelineColorBlendAttachmentState {
VkBool32 blendEnable;
VkBlendFactor srcColorBlendFactor;
VkBlendFactor dstColorBlendFactor;
VkBlendOp colorBlendOp;
VkBlendFactor srcAlphaBlendFactor;
VkBlendFactor dstAlphaBlendFactor;
VkBlendOp alphaBlendOp;
VkColorComponentFlags colorWriteMask;
} VkPipelineColorBlendAttachmentState;
|
La forma más normal para realizar la mezcla es considerar el nivel
de transparencia del source color, que puesto que ha
verificado el test de profundidad debería encontrarse delante del
fragmento antiguo (destination color).
VkPipelineColorBlendAttachmentState colorBlendAttachment = {};
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT
| VK_COLOR_COMPONENT_G_BIT
| VK_COLOR_COMPONENT_B_BIT
| VK_COLOR_COMPONENT_A_BIT;
colorBlendAttachment.blendEnable = VK_TRUE;
colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
VkPipelineColorBlendStateCreateInfo colorBlending = {};
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlending.logicOpEnable = VK_FALSE;
colorBlending.logicOp = VK_LOGIC_OP_COPY; // Optional
colorBlending.attachmentCount = 1;
colorBlending.pAttachments = &colorBlendAttachment;
colorBlending.blendConstants[0] = 0.0f; // Optional
colorBlending.blendConstants[1] = 0.0f; // Optional
colorBlending.blendConstants[2] = 0.0f; // Optional
colorBlending.blendConstants[3] = 0.0f; // Optional
|
|
|
La estructura VkPipelineDynamicStateCreateInfo
|
|
El pipeline contiene muchísima información sobre la configuración de cada
una de sus etapas. Si queremos utilizar configuraciones diferentes al
dibujar distintas partes de la imagen necesitaríamos crear varios
pipelines. La estructura VkPipelineDynamicStateCreateInfo permite definir
un conjunto de valores de la configuración del pipeline que pueden ser
modificados dinámicamente sin necesidad de crear un nuevo pipeline.
typedef struct VkPipelineDynamicStateCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineDynamicStateCreateFlags flags;
uint32_t dynamicStateCount;
const VkDynamicState* pDynamicStates;
} VkPipelineDynamicStateCreateInfo;
|
El campo sType debe ser
VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO.
El campo pNext debe dejarse a nulo.
El campo flags debe dejarse a cero.
El campo dynamicStateCount contiene el
número de variables dinámicas descritas en el siguiente campo.
El campo
pDynamicStates define las variables que se van a configurar
dinámicamente. El tipo de dato VkDynamicState contiene una
enumeración con todas las variables que pueden ser configuradas
dinámicamente. Existen extensiones, como VK_EXT_extended_dynamic_state,
que permiten extender el conjunto de variables que se pueden definir como dinámicas.
Cada posible variable dinámica tiene asociado un comando para
asignar su valor al pipeline.
| VkDynamicState |
Comando |
| VK_DYNAMIC_STATE_VIEWPORT |
vkCmdSetViewport() |
| VK_DYNAMIC_STATE_SCISSOR |
vkCmdSetScissor() |
| VK_DYNAMIC_STATE_LINE_WIDTH |
vkCmdSetLineWidth() |
| VK_DYNAMIC_STATE_DEPTH_BIAS |
vkCmdSetDepthBias() |
| VK_DYNAMIC_STATE_BLEND_CONSTANTS |
vkCmdSetBlendConstants() |
| VK_DYNAMIC_STATE_DEPTH_BOUNDS |
vkCmdSetDepthBounds() |
| VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK |
vkCmdSetStencilCompareMask() |
| VK_DYNAMIC_STATE_STENCIL_WRITE_MASK |
vkCmdSetStencilWriteMask() |
| VK_DYNAMIC_STATE_STENCIL_REFERENCE |
vkCmdSetStencilReference() |
|
|
La estructura VkPipelineLayoutCreateInfo
|
|
La estructura VkPipelineLayoutCreateInfo permite describir
la información almacenada en memoria que van a compartir los shaders.
Esta información se puede representar de dos formas: Descriptor Sets
y Push Constants. Los Descriptor Sets son buffers
donde se suelen almacenar las matrices de transformación o las
imágenes de texturas. Las Push Constants permiten introducir
directamente los valores de las constantes sin necesidad de definir
buffers.
En este proyecto no se van a utilizar ni Descriptor Sets
ni Push Constants. Estas características se explicarán con
mayor detalle en la próxima práctica.
typedef struct VkPipelineLayoutCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineLayoutCreateFlags flags;
uint32_t setLayoutCount;
const VkDescriptorSetLayout* pSetLayouts;
uint32_t pushConstantRangeCount;
const VkPushConstantRange* pPushConstantRanges;
} VkPipelineLayoutCreateInfo;
|
El campo sType debe ser
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO.
El campo pNext debe dejarse a nulo.
El campo flags debe dejarse a cero.
El campo setLayoutCount contiene el número de
Descriptor Sets que se van a definir.
El campo pSetLayouts contiene la definición de los
Descriptor Sets.
El campo pushConstantRangeCount contien el número de
Push Constants que se van a definir.
El campo pPushConstantRanges define las Push Constants
que va a utilizar el pipeline.
|
|
La clase
GEPipelineConfig
|
|
Como acabamos de ver, para crear un objetro VkPipeline es
necesario aportar muchísima información que se engloba en una
estructura VkGraphicsPipelineCreateInfo.
La mayoría de los campos de las diferentes estructuras incluidas en
esta configuración tienen valores comunes en nuestros proyectos.
Para definir las características de los pipelines a crear en
nuestros proyectos vamos a utlizar unas estructuras mucho más
simples que solo contienen los valores de los campos que nos
interesan. La clase que encapsula estas propiedades se ha denominado
GEPipelineConfig. El contenido de su fichero de cabecera es el siguiente:
#pragma once
#include <vulkan/vulkan.h>
#include <vector>
//
// FUNCIÓN: GEPipelineConfig
//
// PROPÓSITO: Almacena la configuración de un pipeline de renderizado
//
class GEPipelineConfig {
public:
int vertex_shader;
int fragment_shader;
int attrStride;
std::vector<VkFormat> attrFormats;
std::vector<int> attrOffsets;
std::vector<VkDescriptorType> descriptorTypes;
std::vector<VkShaderStageFlags> descriptorStages;
VkBool32 depthTestEnable;
VkCullModeFlags cullMode;
VkExtent2D extent;
GEPipelineConfig();
};
|
El constructor de la clase se limita a almacenar los valores por
defecto de los diferentes campos:
#include "GEPipelineConfig.h"
//
// FUNCIÓN: GEPipelineConfig::GEPipelineConfig()
//
// PROPÓSITO: Construye un objeto con los valores por defecto
//
GEPipelineConfig::GEPipelineConfig()
{
vertex_shader = -1;
fragment_shader = -1;
attrStride = 0;
attrFormats.resize(0);
attrOffsets.resize(0);
descriptorTypes.resize(0);
descriptorStages.resize(0);
depthTestEnable = VK_TRUE;
cullMode = VK_CULL_MODE_BACK_BIT;
extent.width = 800;
extent.height = 600;
}
|
|
|
Modificaciones de la clase
GERenderingContext
|
|
La definición del proceso de renderizado se ha incluido dentro de la
clase GERenderingContext. Esto ha llevado a incluir nuevos campos y
métodos en esta clase. Los nuevos campos son:
-
graphicsPipeline: Objeto VkPipeline
con la definición del proceso de renderizado.
-
descriptorSetLayout: Plantilla de
conjuntos de descriptores utilizada en el pipeline, es decir,
descripción de las variables uniformes incluidas en los shaders.
-
pipelineLayout: Plantilla de
renderizado, formada por las plantillas de conjuntos de
descriptores.
-
viewport: Tamaño de las imágenes a
generar.
-
scissor: Fragmento de las imágenes a
generar. Generalmente es el tamaño completo de la imagen.
El contenido del fichero de cabecera de la clase es el siguiente.
#pragma once
#include "GERenderingContext.h"
#include <vulkan/vulkan.h>
#include <iostream>
#include "GEGraphicsContext.h"
#include "GEDrawingContext.h"
#include "GEPipelineConfig.h"
//
// CLASE: GEApplication
//
// DESCRIPCIÓN: Clase que describe un contexto de renderizado
//
class GERenderingContext
{
public:
uint32_t imageCount;
private:
VkFormat format;
VkExtent2D extent;
VkRenderPass renderPass;
VkPipeline graphicsPipeline;
VkDescriptorSetLayout descriptorSetLayout;
VkPipelineLayout pipelineLayout;
VkViewport viewport;
VkRect2D scissor;
public:
GERenderingContext(GEGraphicsContext* gc, GEDrawingContext* dc,
GEPipelineConfig* config);
void destroy(GEGraphicsContext* gc);
private:
// Métodos de creación de componentes
void createRenderPass(GEGraphicsContext* gc);
void createGraphicsPipeline(GEGraphicsContext* gc, GEPipelineConfig* config);
// Métodos de definición del pipeline de renderizado
void createPipelineLayout(GEGraphicsContext* gc, GEPipelineConfig* config);
void createVertexShaderStageCreateInfo(
GEGraphicsContext* gc,
int resource,
VkShaderModule* vertShaderModule,
VkPipelineShaderStageCreateInfo* vertShaderStageInfo);
void createFragmentShaderStageCreateInfo(
GEGraphicsContext* gc,
int resource,
VkShaderModule* fragShaderModule,
VkPipelineShaderStageCreateInfo* fragShaderStageInfo);
void createPipelineVertexInputStateCreateInfo(
GEPipelineConfig* config,
VkPipelineVertexInputStateCreateInfo* vertexInputInfo);
void createPipelineInputAssemblyStateCreateInfo(
VkPipelineInputAssemblyStateCreateInfo* inputAssembly);
void createPipelineViewportStateCreateInfo(
VkPipelineViewportStateCreateInfo* viewportState);
void createPipelineRasterizationStateCreateInfo(
GEPipelineConfig* config,
VkPipelineRasterizationStateCreateInfo* rasterizer);
void createPipelineMultisampleStateCreateInfo(
VkPipelineMultisampleStateCreateInfo* multisampling);
void createPipelineDepthStencilStateCreateInfo(
GEPipelineConfig* config,
VkPipelineDepthStencilStateCreateInfo* depthStencil);
void createPipelineColorBlendStateCreateInfo(
VkPipelineColorBlendAttachmentState* colorBlendAttachment,
VkPipelineColorBlendStateCreateInfo* colorBlending);
// Métodos auxiliares
VkShaderModule createShaderModule(GEGraphicsContext* gc,
const std::vector<char>& code);
std::vector<char> getFileFromResource(int resource);
};
|
La inclusión del pipeline en la clase GERenderingContext requiere
añadir algunos métodos:
-
GERenderingContext(): El constructor
de la clase añade la creación del pipeline.
-
destroy(): El método de destrucción
añade la destrucción del pipeline y de las plantillas de
conjuntos de descriptores.
-
createGraphicsPipeline(): Construye el
pipeline con la configuración indicada. El método llama a
métodos auxilizares dedicados a construir las estructuras
intermedias de la configuración del objeto VkPipeline.
El código de estos métodos y de los nuevos métodos auxiliares se
muestra a continuación.
///////////////////////////////////////////////////////////////////////////////////
///// /////
///// Métodos públicos /////
///// /////
///////////////////////////////////////////////////////////////////////////////////
//
// FUNCIÓN: GERenderingContext::GERenderingContext(...)
//
// PROPÓSITO: Crea un contexto de renderizado
//
GERenderingContext::GERenderingContext(GEGraphicsContext* gc,
GEDrawingContext* dc,
GEPipelineConfig* config)
{
imageCount = dc->getImageCount();
format = dc->getFormat();
extent = dc->getExtent();
createRenderPass(gc);
createGraphicsPipeline(gc, config);
}
//
// FUNCIÓN: GERenderingContext::destroy(GEGraphicsContext* gc)
//
// PROPÓSITO: Crea el estado de Vulkan
//
void GERenderingContext::destroy(GEGraphicsContext* gc)
{
vkDestroyPipeline(gc->device, graphicsPipeline, nullptr);
vkDestroyPipelineLayout(gc->device, pipelineLayout, nullptr);
vkDestroyDescriptorSetLayout(gc->device, descriptorSetLayout, nullptr);
vkDestroyRenderPass(gc->device, renderPass, nullptr);
}
///////////////////////////////////////////////////////////////////////////////////
///// /////
///// Métodos de creación de los componentes /////
///// /////
///////////////////////////////////////////////////////////////////////////////////
//
// FUNCIÓN: GERenderingContext::createGraphicsPipeline()
//
// PROPÓSITO: Crea el Pipeline de renderizado
//
void GERenderingContext::createGraphicsPipeline(GEGraphicsContext* gc,
GEPipelineConfig* config)
{
viewport = {};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float) config->extent.width;
viewport.height = (float) config->extent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
scissor = {};
scissor.offset = { 0, 0 };
scissor.extent = config->extent;
VkShaderModule vertShaderModule, fragShaderModule;
VkPipelineShaderStageCreateInfo vertShaderStageInfo, fragShaderStageInfo;
VkPipelineVertexInputStateCreateInfo vertexInputInfo;
VkPipelineInputAssemblyStateCreateInfo inputAssembly;
VkPipelineViewportStateCreateInfo viewportState;
VkPipelineRasterizationStateCreateInfo rasterizer;
VkPipelineMultisampleStateCreateInfo multisampling;
VkPipelineDepthStencilStateCreateInfo depthStencil;
VkPipelineColorBlendAttachmentState colorBlendAttachment;
VkPipelineColorBlendStateCreateInfo colorBlending;
createPipelineLayout(gc, config);
createVertexShaderStageCreateInfo(gc, config->vertex_shader, &vertShaderModule,
&vertShaderStageInfo);
createFragmentShaderStageCreateInfo(gc, config->fragment_shader, &fragShaderModule,
&fragShaderStageInfo);
VkPipelineShaderStageCreateInfo shaderStages[] = {
vertShaderStageInfo,
fragShaderStageInfo };
createPipelineVertexInputStateCreateInfo(config, &vertexInputInfo);
createPipelineInputAssemblyStateCreateInfo(&inputAssembly);
createPipelineViewportStateCreateInfo(&viewportState);
createPipelineRasterizationStateCreateInfo(config, &rasterizer);
createPipelineMultisampleStateCreateInfo(&multisampling);
createPipelineDepthStencilStateCreateInfo(config, &depthStencil);
createPipelineColorBlendStateCreateInfo(&colorBlendAttachment, &colorBlending);
VkGraphicsPipelineCreateInfo pipelineInfo = {};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = 2;
pipelineInfo.pStages = shaderStages;
pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pViewportState = &viewportState;
pipelineInfo.pRasterizationState = &rasterizer;
pipelineInfo.pMultisampleState = &multisampling;
pipelineInfo.pDepthStencilState = &depthStencil;
pipelineInfo.pColorBlendState = &colorBlending;
pipelineInfo.pDynamicState = nullptr;
pipelineInfo.layout = pipelineLayout;
pipelineInfo.renderPass = renderPass;
pipelineInfo.subpass = 0;
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
if (vkCreateGraphicsPipelines(gc->device, VK_NULL_HANDLE, 1, &pipelineInfo,
nullptr, &graphicsPipeline) != VK_SUCCESS)
{
throw std::runtime_error("failed to create graphics pipeline!");
}
vkDestroyShaderModule(gc->device, fragShaderModule, nullptr);
vkDestroyShaderModule(gc->device, vertShaderModule, nullptr);
std::cout << "Pipeline created!" << std::endl;
}
///////////////////////////////////////////////////////////////////////////////////
///// /////
///// Métodos de definición del pipeline de renderizado /////
///// /////
///////////////////////////////////////////////////////////////////////////////////
//
// FUNCIÓN: CAVulkanState::createPipelineLayout()
//
// PROPÓSITO: Crea el esquema de los conjuntos de descriptores
//
void GERenderingContext::createPipelineLayout(GEGraphicsContext* gc,
GEPipelineConfig* config)
{
uint32_t bindingCount = (uint32_t)config->descriptorTypes.size();
std::vector<VkDescriptorSetLayoutBinding> bindings(bindingCount);
for (uint32_t i = 0; i < bindingCount; i++)
{
VkDescriptorSetLayoutBinding binding = {};
binding.binding = i;
binding.descriptorCount = 1;
binding.descriptorType = config->descriptorTypes[i];
binding.pImmutableSamplers = nullptr;
binding.stageFlags = config->descriptorStages[i];
bindings[i] = binding;
}
VkDescriptorSetLayoutCreateInfo layoutInfo = {};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = bindingCount;
layoutInfo.pBindings = bindings.data();
if (vkCreateDescriptorSetLayout(gc->device, &layoutInfo, nullptr,
&descriptorSetLayout) != VK_SUCCESS)
{
throw std::runtime_error("failed to create descriptor set layout!");
}
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;
pipelineLayoutInfo.pushConstantRangeCount = 0;
if (vkCreatePipelineLayout(gc->device, &pipelineLayoutInfo, nullptr,
&pipelineLayout) != VK_SUCCESS)
{
throw std::runtime_error("failed to create pipeline layout!");
}
}
//
// FUNCIÓN: GERenderingContext::createVertexShaderStageCreateInfo()
//
// PROPÓSITO: Crea la información sobre el Vertex Shader
//
void GERenderingContext::createVertexShaderStageCreateInfo(
GEGraphicsContext* gc,
int resource,
VkShaderModule* vertShaderModule,
VkPipelineShaderStageCreateInfo* vertShaderStageInfo)
{
std::vector<char> vertShaderCode = getFileFromResource(resource);
*vertShaderModule = createShaderModule(gc, vertShaderCode);
*vertShaderStageInfo = {};
vertShaderStageInfo->sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertShaderStageInfo->stage = VK_SHADER_STAGE_VERTEX_BIT;
vertShaderStageInfo->module = *vertShaderModule;
vertShaderStageInfo->pName = "main";
}
//
// FUNCIÓN: GERenderingContext::createFragmentShaderStageCreateInfo()
//
// PROPÓSITO: Crea la información sobre el Vertex Shader
//
void GERenderingContext::createFragmentShaderStageCreateInfo(
GEGraphicsContext* gc,
int resource,
VkShaderModule* fragShaderModule,
VkPipelineShaderStageCreateInfo* fragShaderStageInfo)
{
std::vector<char> fragShaderCode = getFileFromResource(resource);
*fragShaderModule = createShaderModule(gc, fragShaderCode);
*fragShaderStageInfo = {};
fragShaderStageInfo->sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragShaderStageInfo->stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragShaderStageInfo->module = *fragShaderModule;
fragShaderStageInfo->pName = "main";
}
//
// FUNCIÓN: GERenderingContext::createPipelineVertexInputStateCreateInfo()
//
// PROPÓSITO: Crea la descripción de los atributos de los vértices
//
void GERenderingContext::createPipelineVertexInputStateCreateInfo(
GEPipelineConfig* config,
VkPipelineVertexInputStateCreateInfo* vertexInputInfo)
{
std::vector<VkVertexInputBindingDescription> bindingDescriptions;
std::vector<VkVertexInputAttributeDescription> attributeDescriptions;
int attrCount = config->attrOffsets.size();
int bindingCount = (attrCount > 0 ? 1 : 0);
bindingDescriptions.resize(bindingCount);
for (int i = 0; i < bindingCount; i++)
{
bindingDescriptions[i] = {};
bindingDescriptions[i].binding = i;
bindingDescriptions[i].stride = config->attrStride;
bindingDescriptions[i].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
}
attributeDescriptions.resize(attrCount);
for (int i = 0; i < attrCount; i++)
{
attributeDescriptions[i] = {};
attributeDescriptions[i].binding = 0;
attributeDescriptions[i].location = i;
attributeDescriptions[i].format = config->attrFormats[i];
attributeDescriptions[i].offset = config->attrOffsets[i];
}
*vertexInputInfo = {};
vertexInputInfo->sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo->vertexBindingDescriptionCount = bindingCount;
vertexInputInfo->vertexAttributeDescriptionCount = attrCount;
vertexInputInfo->pVertexBindingDescriptions = bindingDescriptions.data();
vertexInputInfo->pVertexAttributeDescriptions = attributeDescriptions.data();
}
//
// FUNCIÓN: GERenderingContext::createPipelineVertexInputStateCreateInfo()
//
// PROPÓSITO: Crea la información para el ensamblado de primitivas
//
void GERenderingContext::createPipelineInputAssemblyStateCreateInfo(
VkPipelineInputAssemblyStateCreateInfo* inputAssembly)
{
*inputAssembly = {};
inputAssembly->sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly->topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly->primitiveRestartEnable = VK_FALSE;
}
//
// FUNCIÓN: CAVulkanState::createPipelineViewportStateCreateInfo()
//
// PROPÓSITO: Crea la información del viewport
//
void GERenderingContext::createPipelineViewportStateCreateInfo(
VkPipelineViewportStateCreateInfo* viewportState)
{
*viewportState = {};
viewportState->sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState->viewportCount = 1;
viewportState->pViewports = &viewport;
viewportState->scissorCount = 1;
viewportState->pScissors = &scissor;
}
//
// FUNCIÓN: GERenderingContext::createPipelineRasterizationStateCreateInfo()
//
// PROPÓSITO: Crea la información de la etapa de rasterización
//
void GERenderingContext::createPipelineRasterizationStateCreateInfo(
GEPipelineConfig* config,
VkPipelineRasterizationStateCreateInfo* rasterizer)
{
*rasterizer = {};
rasterizer->sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer->depthClampEnable = VK_FALSE;
rasterizer->rasterizerDiscardEnable = VK_FALSE;
rasterizer->polygonMode = VK_POLYGON_MODE_FILL;
rasterizer->lineWidth = 1.0f;
rasterizer->cullMode = config->cullMode;
rasterizer->frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
rasterizer->depthBiasEnable = VK_FALSE;
}
//
// FUNCIÓN: GERenderingContext::createPipelineRasterizationStateCreateInfo()
//
// PROPÓSITO: Crea la información de la etapa de sampleado
//
void GERenderingContext::createPipelineMultisampleStateCreateInfo(
VkPipelineMultisampleStateCreateInfo* multisampling)
{
*multisampling = {};
multisampling->sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling->sampleShadingEnable = VK_FALSE;
multisampling->rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
}
//
// FUNCIÓN: GERenderingContext::createPipelineRasterizationStateCreateInfo()
//
// PROPÓSITO: Crea la información sobre los tests de profundidad y de plantilla
//
void GERenderingContext::createPipelineDepthStencilStateCreateInfo(
GEPipelineConfig* config,
VkPipelineDepthStencilStateCreateInfo* depthStencil)
{
*depthStencil = {};
depthStencil->sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depthStencil->pNext = nullptr;
depthStencil->flags = 0;
depthStencil->depthTestEnable = config->depthTestEnable;
depthStencil->depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
depthStencil->depthWriteEnable = VK_TRUE;
depthStencil->depthBoundsTestEnable = VK_FALSE;
depthStencil->stencilTestEnable = VK_FALSE;
}
//
// FUNCIÓN: GERenderingContext::createPipelineColorBlendStateCreateInfo()
//
// PROPÓSITO: Crea la información sobre la etapa de mezcla de colores
//
void GERenderingContext::createPipelineColorBlendStateCreateInfo(
VkPipelineColorBlendAttachmentState* colorBlendAttachment,
VkPipelineColorBlendStateCreateInfo* colorBlending)
{
*colorBlendAttachment = {};
colorBlendAttachment->colorWriteMask = VK_COLOR_COMPONENT_R_BIT |
VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT |
VK_COLOR_COMPONENT_A_BIT;
colorBlendAttachment->blendEnable = VK_FALSE;
*colorBlending = {};
colorBlending->sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlending->logicOpEnable = VK_FALSE;
colorBlending->logicOp = VK_LOGIC_OP_COPY;
colorBlending->attachmentCount = 1;
colorBlending->pAttachments = colorBlendAttachment;
colorBlending->blendConstants[0] = 0.0f;
colorBlending->blendConstants[1] = 0.0f;
colorBlending->blendConstants[2] = 0.0f;
colorBlending->blendConstants[3] = 0.0f;
}
|
|
|
Modificaciones de la clase
GEApplication
|
|
Para construir un objeto GERenderingContext es necesario definir una
estructura GEPipelineConfig. Esto obliga a modificar la clase
GEApplication para incluir la construcción de esta estructura. En
este proyecto el pipeline es muy básico ya que no existen atributos
de entrada ni variables uniformes por lo que el contenido de la
estructura de configuración es bastante simple. Los métodos
modificados de la clase GEApplication se muestran a continuación.
//
// 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());
GEPipelineConfig* config = getPipelineConfig(dc->getExtent());
this->rc = new GERenderingContext(this->gc, this->dc, config);
mainLoop();
cleanup();
}
//
// FUNCIÓN: GEApplication::getPipelineConfig()
//
// PROPÓSITO: Obtiene la configuración del pipeline de renderizado
//
GEPipelineConfig* GEApplication::getPipelineConfig(VkExtent2D extent)
{
GEPipelineConfig* config = new GEPipelineConfig();
config->vertex_shader = IDR_HTML1;
config->fragment_shader = IDR_HTML2;
config->attrStride = 0;
config->attrOffsets.resize(0);
config->attrFormats.resize(0);
config->descriptorTypes.resize(0);
config->descriptorStages.resize();
config->depthTestEnable = VK_TRUE;
config->cullMode = VK_CULL_MODE_NONE;
config->extent = extent;
return config;
}
|
|
|
Aspecto final
|
|
El
único cambio respecto a la aplicación es el mensaje presentado en
consola.
|
|