渲染管线介绍
图形管线是将网格的顶点和纹理一直到渲染目标中的像素的操作序列。下面是一个简单的概述:
- input assembler 获取顶点数据,顶点数据的来源可以是应用程序提交的原始顶点数据,或是根据索引缓冲提取的顶点数据。
- vertex shader 对每个顶点进行模型空间到屏幕空间的变换,然后将顶点数据传递给图形管线的下一阶段。
- tessellation shaders 根据一定的规则对几何图形进行细分,从而提高网格质量。通常被用来使类似墙面这类不光滑 表面看起来更自然。
- geometry shader 以图元 (三角形,线段,点) 为单位处理几何图形,它可以剔除图元,输出图元。有点类似于 tessellation shader,但更灵活。但目前已经不推荐应用程序使用它,geometry shader 的性能在除了 Intel 集成显卡外的大多数显卡上表现不佳。
- rasterization 阶段将图元离散为片段。片段被用来在帧缓冲上填充像素。位于屏幕外的片段会被丢弃,顶点着色器输出的顶点属性会在片段之间进行插值,开启深度测试后,位于其它片段之后的片段也会被丢弃。
- fragment shader 对每一个未被丢弃的片段进行处理,确定片段要写入的帧缓冲,它可以使用来自 vertex shader 的插值数据,比如纹理坐标和顶点法线。
- color blending 阶段对写入帧缓冲同一像素位置的不同片段进行混合操作。片段可以直接覆盖之前写入的片段,也可以基于之前片段写入的信息进行混合操作。
- 使用绿色标识的阶段也被叫做固定功能阶段。固定功能阶段允许通过参数对处理过程进行一定程度的配置。 使用橙色标识的阶段是可编程阶段,允许我们将自己的代码加载到显卡,进行我们想要的操作。这使得我们可以实现许多有趣的效果。我们加载到显卡的代码会被 GPU 并行处理。
- 在 Vulkan 中,图形管线几乎完全不允许进行动态设置,如果我们想使用其它着色器,绑定其它帧缓冲,以及改变混合函数,都需要重新创建管线。这就迫使我们必须提前创建所有我们需要使用的图形管线,虽然这样看起来不太方便,但这给驱动程序带来了很大的优化空间 。
创建着色器模块
Vulkan使用SPIR-V字节码格式作为着色器代码,在创建着色器模块时,通过读取的字节码创建相应的着色器模块。
VkShaderModuleCreateInfo ——指定新创建的着色器模块参数的结构体
// Provided by VK_VERSION_1_0
typedef struct VkShaderModuleCreateInfo {
VkStructureType sType; //标识该结构体的类型
const void* pNext; //指向扩展该结构体的指针
VkShaderModuleCreateFlags flags; //预留值
size_t codeSize; //着色器字节码的大小
const uint32_t* pCode; //指向着色器字节码的指针
} VkShaderModuleCreateInfo;
在填写完着色器模块信息后,调用vkCreateShaderModule 函数创建 VkShaderModule 对象:
// Provided by VK_VERSION_1_0
VkResult vkCreateShaderModule(
VkDevice device,
const VkShaderModuleCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkShaderModule* pShaderModule);
在创建完渲染管线后,我们可以调用vkDestroyShaderModule删除该渲染管线使用的着色器模块。
// Provided by VK_VERSION_1_0
void vkDestroyShaderModule(
VkDevice device,
VkShaderModule shaderModule,
const VkAllocationCallbacks* pAllocator);
创建着色器渲染管线
为了实际使用着色器,我们需要通过VkPipelineShaderStageCreateInfo结构将它们分配到特定的管道阶段,作为实际管线创建过程的一部分。 我们通过填写VkPipelineShaderStageCreateInfo来绑定我们之前创建的着色器模块,并指定某个着色器阶段使用的着色器模块。
// Provided by VK_VERSION_1_0
typedef struct VkPipelineShaderStageCreateInfo {
VkStructureType sType; //标识该结构体类型
const void* pNext; //扩展该结构体的指针
VkPipelineShaderStageCreateFlags flags; //位掩码,指定如何生成管线着色器阶段。
VkShaderStageFlagBits stage; //指定着色器在渲染的哪个阶段使用
VkShaderModule module; //绑定该阶段使用的着色器模块
const char* pName; //指定此阶段着色器的入口点名称。
const VkSpecializationInfo* pSpecializationInfo; //指向VkSpecializationInfo结构的指针,如Specialization Constants中所述,或NULL。
} VkPipelineShaderStageCreateInfo;
pSpecializationInfo,允许你指定着色器常量的值。你可以使用一个着色器模块,它的行为可以在创建管道时通过为其中使用的常量指定不同的值来配置。这比在渲染时使用变量配置着色器更高效,因为编译器可以进行优化,比如消除依赖于这些值的if语句。如果没有这样的常量,可以将成员设置为nullptr,结构体的初始化会自动完成。如下所示,我们创建了顶点着色器和片段着色器的着色器片段。
VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertShaderStageInfo.module = vertShaderModule;
vertShaderStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragShaderStageInfo.module = fragShaderModule;
fragShaderStageInfo.pName = "main";
固定功能
旧的图形api为图形管线的大多数阶段提供默认状态。在Vulkan中,你必须明确大多数管线阶段,因为它将被确定成一个不可变的管线阶段对象。
动态阶段
虽然大多数管线阶段需要被嵌入到渲染管线中,但在绘制时无需重新创建管道,就可以更改有限数量的状态。例如视口的大小,线宽和混合常数。如果你想使用动态阶段并保留这些属性,那么你必须像这样填写一个VkPipelineDynamicStateCreateInfo:
std::vector<VkDynamicState> dynamicStates = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
VkPipelineDynamicStateCreateInfo dynamicState{};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
dynamicState.pDynamicStates = dynamicStates.data();
这将导致这些值的配置被忽略,您将能够(并且需要)在绘图时指定数据。这使得设置更加灵活,对于视口和剪刀状态等情况非常常见,而在管道状态中则会导致更复杂的设置。
顶点输入
VkPipelineVertexInputStateCreateInfo结构描述了将传递给顶点着色器的顶点数据的格式。它大致用两种方式描述了这一点:
- 绑定:数据之间的间距和数据是按逐顶点的方式还是按逐实例的方式进行组织
- 属性描述:传递给顶点着色器的属性类型,用于将属性绑定到顶点着色器中的变量
// Provided by VK_VERSION_1_0
typedef struct VkPipelineVertexInputStateCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineVertexInputStateCreateFlags flags;
uint32_t vertexBindingDescriptionCount;
const VkVertexInputBindingDescription* pVertexBindingDescriptions;
uint32_t vertexAttributeDescriptionCount;
const VkVertexInputAttributeDescription* pVertexAttributeDescriptions;
} VkPipelineVertexInputStateCreateInfo;
我们需要告诉Vulkan如何将这个数据格式传递给顶点着色器。传递此信息需要两种类型的结构。
VkVertexInputBindingDescription —— 指定顶点输入绑定描述的结构体
顶点绑定描述了在整个顶点从内存加载数据的速率。它指定数据项之间的字节数,以及在每个顶点之后还是在每个实例之后移动到下一个数据项。
我们所有的顶点数据都打包在一个数组中,所以我们只需要一个绑定。binding参数指定binding在binding数组中的索引。stride参数指定从一个条目到下一个条目的字节数,inputRate参数可以有以下值之一
// Provided by VK_VERSION_1_0
typedef struct VkVertexInputBindingDescription {
uint32_t binding;
uint32_t stride;
VkVertexInputRate inputRate;
} VkVertexInputBindingDescription;
- Binding是这个结构描述的绑定号。
- stride是缓冲区内连续元素之间的字节步幅。
- inputRate指定顶点属性寻址是顶点索引的函数还是实例索引的函数。
VkVertexInputAttributeDescription -指定顶点输入属性描述的结构
binding参数告诉Vulkan每个顶点数据是从哪个绑定来的,location参数引用顶点着色器中输入的location指令,format参数描述属性的数据类型,offset参数指定从每个顶点数据开始读取以来的字节数。
// Provided by VK_VERSION_1_0
typedef struct VkVertexInputAttributeDescription {
uint32_t location;
uint32_t binding;
VkFormat format;
uint32_t offset;
} VkVertexInputAttributeDescription;
- location是这个属性的着色器输入位置号。
- binding是此属性从中获取数据的绑定号。
- format是顶点属性数据的大小和类型。
- offset是该属性相对于顶点输入绑定中元素起始位置的字节偏移量。
const std::vector<float> 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
};
VkVertexInputBindingDescription bindingDescription = {};
bindingDescription.binding = 0;
bindingDescription.stride = 5 * sizeof(float);
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
VkVertexInputAttributeDescription attributeDescriptions[2] = {};
attributeDescriptions[0].binding = 0;
attributeDescriptions[0].location = 0;
attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[0].offset = 0;
attributeDescriptions[1].binding = 0;
attributeDescriptions[1].location = 1;
attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
attributeDescriptions[1].offset = 2*sizeof(float);
VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
vertexInputInfo.vertexAttributeDescriptionCount = 2;
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions;
顶点缓冲区分配
Vulkan中的缓冲区是一块存储了可以被显卡读取的任意数据的内存区域,它们可以用来存储顶点数据。与我们到目前为止一直在处理的Vulkan对象不同,缓冲区不会自动为自己分配内存。前几章的工作已经表明,Vulkan API使程序员能够控制几乎所有的事情,内存管理就是其中之一。
首先填写buffer创建结构体从而在显卡上申请一块缓存,然后通过vkCreateBuffer创建buffer缓存,最后在程序结束前通过vkDestroyBuffer销毁缓存。
// Provided by VK_VERSION_1_0
VkBufferCreateInfo
- Structure specifying the parameters of a newly created buffer object
typedef struct VkBufferCreateInfo {
VkStructureType sType;
const void* pNext;
VkBufferCreateFlags flags;
VkDeviceSize size;
VkBufferUsageFlags usage;
VkSharingMode sharingMode;
uint32_t queueFamilyIndexCount;
const uint32_t* pQueueFamilyIndices;
} VkBufferCreateInfo;
- flags是VkBufferCreateFlagBits的位掩码,指定了缓冲区的其他参数。
- Size是要创建的缓冲区的字节大小。
- usage是一个VkBufferUsageFlagBits 的位掩码,指定了缓冲区允许的使用情况。
- sharingMode是 VkSharingMode 值,指定缓冲区被多个队列族访问时的共享模式。
- queueFamilyIndexCount是pQueueFamilyIndices数组项的数目。
- pQueueFamilyIndices是一个指针,指向将访问该缓冲区的队列族数组。如果共享模式不是VK_SHARING_MODE_CONCURRENT,则忽略该值。
VkBufferCreateInfo bufferInfo{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = sizeof(vertices[0]) * vertices.size();
bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (vkCreateBuffer(device, &bufferInfo, nullptr, &vertexBuffer) != VK_SUCCESS) {
throw std::runtime_error("failed to create vertex buffer!");
}
内存需求查询
缓冲区已经创建,但实际上还没有为它分配任何内存。为缓冲区分配内存的第一步是使用vkGetBufferMemoryRequirements函数查询其内存需求。
VkMemoryRequirements memRequirements;
vkGetBufferMemoryRequirements(device, vertexBuffer, &memRequirements);
VkMemoryRequirements 结构体有三个成员变量:
size
: 所需的内存大小, 可能和bufferInfo.size不同
.alignment
: 缓冲区在分配的内存区域中开始的偏移量,受bufferInfo.usage和
bufferInfo.flags两个字段影响
.memoryTypeBits
: 适合该缓冲区的内存类型的位字段。
显卡可以提供不同类型的内存来进行分配。每种类型的内存在允许的操作和性能特征方面有所不同。我们需要将缓冲区的需求和我们自己的应用程序需求结合起来,以找到要使用的正确类型的内存。
VkPhysicalDeviceMemoryProperties memProperties;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
VkPhysicalDeviceMemoryProperties结构有两个数组memoryTypes和memoryheap。内存堆是不同的内存资源,如专用VRAM和RAM中的交换空间,以备VRAM耗尽时使用。这些堆中存在不同类型的内存。现在我们只关注内存的类型,而不是它来自的堆,但你可以想象这可能会影响性能。
// Provided by VK_VERSION_1_0
typedef struct VkPhysicalDeviceMemoryProperties {
uint32_t memoryTypeCount;
VkMemoryType memoryTypes[VK_MAX_MEMORY_TYPES];
uint32_t memoryHeapCount;
VkMemoryHeap memoryHeaps[VK_MAX_MEMORY_HEAPS];
} VkPhysicalDeviceMemoryProperties;
memoryTypeCount:
memoryTypes数组中有效元素的数量。
memoryTypes:
VkMemoryType 类型的结构,描述可用于访问由memoryHeaps指定的堆分配的内存的内存类型。
memoryHeapCount:
memoryHeaps数组中有效元素的数量
memoryHeaps:
VkMemoryHeap结构体数组,描述可以从中分配内存的内存堆。
让我们首先找到一个适合缓冲区本身的内存类型,typeFilter参数将用于指定合适的内存类型的位字段。这意味着我们可以通过简单地遍历它们并检查相应的位是否被设置为1来找到合适的内存类型的索引。
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
if (typeFilter & (1 << i)) {
return i;
}
}
然而,我们不只是感兴趣的内存类型适用于顶点缓冲。我们还需要能够将顶点数据写入内存。memoryTypes数组由VkMemoryType结构体组成,这些结构体指定了每种类型内存的堆和属性。属性定义了内存的特殊特性,比如能够映射它,以便我们可以从CPU写入它。这个属性与VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT表示,但我们也需要使用VK_MEMORY_PROPERTY_HOST_COHERENT_BIT属性。
uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
VkPhysicalDeviceMemoryProperties memProperties;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
if (typeFilter & (1 << i) && (memProperties.memoryTypes[i].propertyFlags & properties)) {
return i;
}
}
throw std::runtime_error("failed to find suitable memory type!");
}
内存分配
现在我们有一种方法来确定正确的内存类型,因此我们可以通过填充VkMemoryAllocateInfo结构来实际分配内存。
VkMemoryAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
if (vkAllocateMemory(device, &allocaInfo, nullptr, &vertexBufferMemory) != VK_SUCCESS) {
throw std::runtime_error("failed to allocate vertex buffer memory!");
}
// Provided by VK_VERSION_1_0
typedef struct VkMemoryAllocateInfo {
VkStructureType sType;
const void* pNext;
VkDeviceSize allocationSize;
uint32_t memoryTypeIndex;
} VkMemoryAllocateInfo;
在分配内存之后,我们需要将缓冲区与内存区域绑定起来,并在销毁缓冲区之后销毁分配给它的内存:
vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0);
vkDestroyBuffer(device, vertexBuffer, nullptr);
vkFreeMemory(device, vertexBufferMemory, nullptr);
填充顶点缓冲区
现在,我们可以开始将顶点数据复制到缓冲中。我们需要使用 vkMapMemory 函数将缓冲关联的内存映射到 CPU 可以访问的内存。
获取指向可映射内存对象区域的主机虚拟地址指针
// Provided by VK_VERSION_1_0
VkResult vkMapMemory(
VkDevice device,
VkDeviceMemory memory,
VkDeviceSize offset,
VkDeviceSize size,
VkMemoryMapFlags flags,
void** ppData);
- Device是拥有内存的逻辑设备。
- memory是要映射的vkdevicemmemory对象。
- Offset是从内存对象的起始位置开始的字节偏移量。
- size是要映射的内存范围的长度,或VK_WHOLE_SIZE要映射的内存范围从偏移量到分配结束的长度。标志将保留以供将来使用。
- ppData是一个指向void*变量的指针,返回一个指向映射范围起始位置的主机可访问指针。这个指针减去偏移量必须至少对齐到VkPhysicalDeviceLimits::minMemoryMapAlignment。
现在可以使用 memcpy 将顶点数据复制到映射后的内存,然后调用 vkUnmapMemory 函数来结束内存映射。然而,驱动程序可能并不会立即复制数据到缓冲关联的内存中去,这是由于现代处理器都存在缓存这一设计,写入内 存的数据并不一定在多个核心同时可见,有下面两种方法可以保证数据被立即复制到缓冲关联的内存中去:
- 使用带有 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT 属性的内存类型,保证内存可见的 一致性
- 写入数据到映射的内存后,调用 vkFlushMappedMemoryRanges 函数,读取映射的内存数据前调用 vkInvalidateMappedMemoryRanges 函数
void* data;
vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data);
memcpy(data, vertices.data(), (size_t) bufferInfo.size);
vkUnmapMemory(device, vertexBufferMemory);
输入装配
VkPipelineInputAssemblyStateCreateInfo 结构体用于描述两个信息:顶点数据定义了哪种类型的几何图元,以及是否启用几何图元重启。前一个信息通过 topology 成员变量指定,它的值可以是下面这些:
// Provided by VK_VERSION_1_0
typedef struct VkPipelineInputAssemblyStateCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineInputAssemblyStateCreateFlags flags;
VkPrimitiveTopology topology;
VkBool32 primitiveRestartEnable;
} VkPipelineInputAssemblyStateCreateInfo;
-
topology:
定义图元拓扑结构的 VkPrimitiveTopology的类型,可以是下面这些值:
- VK_PRIMITIVE_TOPOLOGY_POINT_LIST:点图元
- VK_PRIMITIVE_TOPOLOGY_LINE_LIST:每两个顶点构成一个线段图元
- VK_PRIMITIVE_TOPOLOGY_LINE_STRIP:每两个顶点构成一个线段图元,除第一个线段图元外, 每个线段图元使用上一个线段图元的一个顶点
- VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST:每三个顶点构成一个三角形图元
- VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP:每个三角形的第二个和第三个顶点被下一个三 角形作为第一和第二个顶点使用
-
primitiveRestartEnable:
控制是否将特殊顶点索引值视为重新启动原语集. This enable only applies to indexed draws (vkCmdDrawIndexed, vkCmdDrawMultiIndexedEXT, and vkCmdDrawIndexedIndirect), and the special index value is either 0xFFFFFFFF when theindexType
parameter ofvkCmdBindIndexBuffer2KHR
orvkCmdBindIndexBuffer
is equal toVK_INDEX_TYPE_UINT32
, 0xFF whenindexType
is equal toVK_INDEX_TYPE_UINT8_EXT
, or 0xFFFF whenindexType
is equal toVK_INDEX_TYPE_UINT16
. Primitive restart is not allowed for “list” topologies, unless one of the features primitiveTopologyPatchListRestart (forVK_PRIMITIVE_TOPOLOGY_PATCH_LIST
) or primitiveTopologyListRestart (for all other list topologies) is enabled.
VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;
视口与裁剪
viewport描述了输出将被渲染到帧缓冲上的区域,绝大多数的情况下总是(0,0)到((width, height)。
VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float) swapChainExtent.width;
viewport.height = (float) swapChainExtent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
x
,y是视口的左上角坐标
width,
height是视口的宽高
minDepth,
maxDepth是视口所在区域的深度范围
视口定义了从图像到帧缓冲区的转换,裁剪区域定义了像素实际存储在哪个区域。在裁剪形矩形之外的任何像素都将在光栅化阶段丢弃。它们的功能更像是过滤器,而不是转换。区别如下所示。注意,右边的裁剪矩形只是产生该图像的众多可能性之一,只要它比视口区域大。
因此,如果我们想绘制整个帧缓冲区,我们将指定一个剪刀矩形覆盖整个帧缓冲区:
VkRect2D scissor{};
scissor.offset = {0, 0};
scissor.extent = swapChainExtent;
Viewport(s)和scissor rectangle(s)既可以指定为管道的静态部分,也可以指定为命令缓冲区中的动态设置。虽然前者更符合其他状态,但将视口和裁剪状态设置为动态状态通常更方便,因为它提供了更多灵活性。这是很常见的,所有实现都可以处理这种动态状态而不会造成性能损失。
std::vector<VkDynamicState> dynamicStates = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
VkPipelineDynamicStateCreateInfo dynamicState{};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
dynamicState.pDynamicStates = dynamicStates.data();
如果没有设置动态阶段,视口和裁剪矩形需要组合在一起,通过 VkPipelineViewportStateCreateInfo 结构体指定。这使得这个管道的视口和剪刀矩形不可变。对这些值的任何更改都需要用新值创建一个新的管道。
VkPipelineViewportStateCreateInfo viewportState{};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.pViewports = &viewport;
viewportState.scissorCount = 1;
viewportState.pScissors = &scissor;
光栅化
光栅化器从顶点着色器中获取由顶点构成的几何图形,并将其转换为片段,以便由片段着色器着色。它还可以执行深度测试,面剔除和裁剪测试,它可以配置为输出填充整个多边形或仅边缘的片段(线框渲染)。所有这些都是使用VkPipelineRasterizationStateCreateInfo结构进行配置的。光栅化可以通过添加一个常量或根据片段的斜率对其进行偏置来改变深度值。这有时用于阴影映射,但我们不会使用它。只需将depthBiasEnable设置为VK_FALSE。
// Provided by VK_VERSION_1_0
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;
depthClampEnable:
设置为 VK_TRUE 表示在近平面和远平面外的片段会被截断为在近平面和远平面 上,而不是直接丢弃这些片段。
rasterizerDiscardEnable:
控制是否在光栅化阶段之前立即丢弃原语,这一设置会禁 止一切片段输出到帧缓冲
polygonMode:
用于指定几何图元生成片段的方式,见VkPolygonMode。
- VK_POLYGON_MODE_FILL:整个多边形,包括多边形内部都产生片段
- VK_POLYGON_MODE_LINE:只有多边形的边会产生片段
- VK_POLYGON_MODE_POINT:只有多边形的顶点会产生片段
cullMode:
用于原语剔除的三角形面向方向,见VkCullModeFlagBits.。
frontFace:
指定面向正面的三角形方向,见VkFrontFace 。
depthBiasEnable:
控制片段深度值是否偏置.
depthBiasConstantFactor:
是一个标量,控制添加到每个片段的恒定深度值。
depthBiasClamp:
是片段的最大(或最小)深度偏差。
depthBiasSlopeFactor:
是在深度偏差计算中应用于片段斜率的标量。.
lineWidth:光栅化线段的宽度
VkPipelineRasterizationStateCreateInfo rasterizer = {};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthBiasClamp = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
rasterizer.lineWidth = 1.0f;
rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
rasterizer.depthBiasEnable = VK_FALSE;
rasterizer.depthBiasConstantFactor = 0.0f; // Optional
rasterizer.depthBiasClamp = 0.0f; // Optional
rasterizer.depthBiasSlopeFactor = 0.0f; // Optional
多重采样
VkPipelineMultisampleStateCreateInfo结构配置多重采样,这是执行抗锯齿的方法之一。它的工作原理是将光栅化到相同像素的多个多边形的片段着色器结果组合在一起。这主要发生在边缘,这也是最明显的锯齿发生的地方。对于一个像素只被一个多边形产生的片段覆盖,只会对覆盖它的这个片段执行一次片段着色器,使用多重采样进行反走样的代价要比使用更高的分辨率渲染,然后缩小图像达到反走样的代价小得多,目前我们不开启多重采样。
VkPipelineMultisampleStateCreateInfo multisampling{};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
multisampling.minSampleShading = 1.0f; // Optional
multisampling.pSampleMask = nullptr; // Optional
multisampling.alphaToCoverageEnable = VK_FALSE; // Optional
multisampling.alphaToOneEnable = VK_FALSE; // Optional
rasterizationSamples:
是一个VkSampleCountFlagBits值,指定光栅化中进行多重采样的数量。如果管道是用VK_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT动态状态集创建的,那么为了设置光栅化中多重采样的像素值数量,这个值会被忽略,但是如果没有设置VK_DYNAMIC_STATE_SAMPLE_MASK_EXT动态状态,它仍然被用来定义pSampleMask数组的大小,如下所述。
sampleShadingEnable:是否开启
Sample Shading.
minSampleShading:
如果sampleShadingEnable设置为VK_TRUE,则指定样本着色的最小比例。
pSampleMask
是 指向 sample mask test中使用的VkSampleMask值数组的指针。
alphaToCoverageEnable
控制是否根据片段的第一种颜色输出的alpha分量生成临时覆盖值,如 Multisample Coverage部分中指定的。
alphaToOneEnable
控制片段的第一种颜色输出的alpha分量是否被 Multisample Coverage中所描述的替换。
颜色混合
片段着色器返回的片段颜色需要和原来帧缓冲中对应像素的颜色进行混合。混合的方式有下面两种:
- 混合旧值和新值产生最终的颜色
- 使用位运算组合旧值和新值
有两个用于配置颜色混合的结构体。第一个是 VkPipelineColorBlendAttachmentState 结构体,可以用它来对每个绑定的帧缓冲进行单独的颜色混合配置。第二个是 VkPipelineColorBlendStateCreateInfo 结构体,可以用它来进行全局的颜色混合配置。
//Structure specifying a pipeline color blend attachment state
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_FALSE;
colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // Optional
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; // Optional
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; // Optional
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; // Optional
blendEnable:
控制是否为相应的颜色附件启用混合。如果未启用混合,则该附件的源片段的颜色将未经修改地传递。
srcColorBlendFactor:
选择使用哪个混合因子来确定源因子(Sr,Sg,Sb)。
dstColorBlendFactor:
选择使用哪个混合因子来确定目标因子(Dr,Dg,Db)。
colorBlendOp:
选择使用哪个混合操作来计算要写入颜色附件的RGB值。
srcAlphaBlendFactor:
选择使用哪个混合因子来确定源因子Sa。
dstAlphaBlendFactor:
选择使用哪个混合因子来确定目标因子Da。
alphaBlendOp:
选择使用哪个混合操作来计算写入颜色附件的alpha值。
colorWriteMask:
VkColorComponentFlagBits的位掩码,指定R、G、B和/或a组件中的哪一个可以写入,如颜色写入掩码所述。
VkPipelineColorBlendStateCreateInfo 结构体使用了一个 VkPipelineColorBlendAttachmentState 结构体数组指针来指定每个帧缓冲的颜色混合设置,还提供了用于设置全局混合常量的成员变量。
// Structure specifying parameters of a newly created pipeline color blend state
// Provided by VK_VERSION_1_0
typedef struct VkPipelineColorBlendStateCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineColorBlendStateCreateFlags flags;
VkBool32 logicOpEnable;
VkLogicOp logicOp;
uint32_t attachmentCount;
const VkPipelineColorBlendAttachmentState* pAttachments;
float blendConstants[4];
} VkPipelineColorBlendStateCreateInfo;
flags:
VkPipelineColorBlendStateCreateFlagBits 的位掩码,指定额外的颜色混合信息。
logicOpEnable:
控制是否应用 Logical Operations.
logicOp:
选择要应用的逻辑操作。
attachmentCount:
VkPipelineColorBlendAttachmentState 元素在pAttachments的数量. 如果管线创建时设置了VK_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT、VK_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT和VK_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT动态状态,并且设备上没有启用VK_DYNAMIC_STATE_COLOR_BLEND_ADVANCED_EXT或advancedBlendCoherentOperations,则忽略该选项。
pAttachments:
指向 VkPipelineColorBlendAttachmentState结构数组的指针,这些结构定义了每个颜色附件的混合状态。如果管线创建时设置了VK_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT、VK_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT和VK_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT动态状态,并且设备上没有启用VK_DYNAMIC_STATE_COLOR_BLEND_ADVANCED_EXT或advancedBlendCoherentOperations,则忽略该选项。
blendConstants:
指向一个包含四个值的数组,这些值用作混合常数的R、G、B和a组件,用于混合,具体取决于混合因子。
管线布局
我们可以在着色器中使用 uniform 变量,它可以在管线建立后动态地被应用程序修改,实现对着色器进行一定程度 的动态配置。uniform 变量经常被用来传递变换矩阵给顶点着色器,以及传递纹理采样器句柄给片段着色器。 我们在着色器中使用的 uniform 变量需要在管线创建时使用 VkPipelineLayout 对象定义。暂时,我们不使用 uniform 变量,但我们仍需要创建一个 VkPipelineLayout 对象,指定空的管线布局。
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.pNext = nullptr;
pipelineLayoutInfo.flags = VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT;
pipelineLayoutInfo.setLayoutCount = 0; // Optional
pipelineLayoutInfo.pSetLayouts = nullptr; // Optional
pipelineLayoutInfo.pushConstantRangeCount = 0; // Optional
pipelineLayoutInfo.pPushConstantRanges = nullptr; // Optional
if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) {
throw std::runtime_error("failed to create pipeline layout!");
}
- flags是VkPipelineLayoutCreateFlagBits的位掩码,指定管线布局创建的选项。
- setLayoutCount是管线布局中包含的描述符集的数量。
- pSetLayouts是指向VkDescriptorSetLayout对象数组的指针。
- pushConstantRangeCount是管线布局中包含的输入常量范围的数量。
- pPushConstantRanges是一个指向VkPushConstantRange结构数组的指针,该数组定义了一组用于单个管道布局的push常量范围。除了描述符集布局之外,管道布局还描述了管道的每个阶段可以访问多少输入常量。
并在应用程序结束之前清除管线布局对象:
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
渲染流程
在进行管线创建之前,我们还需要设置用于渲染的帧缓冲附着。我们需要指定使用的颜色和深度缓冲,以及采样数,渲染操作如何处理缓冲的内容。所有这些信息被 Vulkan 包装为一个渲染流程对象。
附着描述
在这里,我们只使用了一个代表交换链图像的颜色缓冲附着。 format 成员变量用于指定颜色缓冲附着的格式。samples 成员变量用于指定采样数,在这里,我们没有使用多重采 样,所以将采样数设置为 1。loadOp 和 storeOp 成员变量用于指定在渲染之前和渲染之后对附着中的数据进行的操作。对于 loadOp 成员变量, 可以设置为下面这些值:
- VK_ATTACHMENT_LOAD_OP_LOAD:保持附着的现有内容
- VK_ATTACHMENT_LOAD_OP_CLEAR:使用一个常量值来清除附着的内容
- VK_ATTACHMENT_LOAD_OP_DONT_CARE:不关心附着现存的内容
storeOp 成员变量可以设置为下面这些值:
- VK_ATTACHMENT_STORE_OP_STORE:渲染的内容会被存储起来,以便之后读取
- VK_ATTACHMENT_STORE_OP_DONT_CARE:渲染后,不会读取帧缓冲的内容
Vulkan 中的纹理和帧缓冲由特定像素格式的 VkImage 对象来表示。图像的像素数据在内存中的分布取决于我们要 对图像进行的操作。 下面是一些常用的图形内存布局设置:
- VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:图像被用作颜色附着
- VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:图像被用在交换链中进行呈现操作
- VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:图像被用作复制操作的目的图像
initialLayout 成员变量用于指定渲染流程开始前的图像布局方式。finalLayout 成员变量用于指定渲染流程结束后的图像布局方式。将 initialLayout 成员变量设置为 VK_IMAGE_LAYOUT_UNDEFINED 表示我们不关心之前的图像布局方式。使用这一值后,图像的内容不保证会被保留,但对于我们的应用程序, 每次渲染前都要清除图像,所以这样的设置更符合我们的需求。对于 finalLayout 成员变量,我们设置为 VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,使得渲染后的图像可以被交换链呈现。
VkAttachmentDescription colorAttachment = {};
colorAttachment.format = swapChainImageFormat;
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
format:
VkFormat值,指定将用于附件的图像视图的格式。
samples:
是一个VkSampleCountFlagBits值,指定图像的采样数。
loadOp:
一个VkAttachmentLoadOp值,指定附着的颜色和深度组件的内容在第一次使用的子通道开始时如何处理。
storeOp:
一个VkAttachmentStoreOp值,指定附件的颜色和深度组件的内容在最后使用它的子通道结束时如何处理。
stencilLoadOp:
一个VkAttachmentLoadOp值,指定附件的模板组件的内容在第一次使用的子通道开始时如何处理。
stencilStoreOp:
VkAttachmentStoreOp值,指定在使用附件的最后一个子通道结束时如何处理附件的模板组件的内容。
initialLayout:
当渲染通道实例开始时,附着图像子资源将处于的布局。
finalLayout:
当渲染传递实例结束时,附件图像子资源将转换到的布局。
子流程和附着引用
一个渲染流程可以包含多个子流程。子流程依赖于上一流程处理后的帧缓冲内容。比如,许多叠加的后期处理效果 就是在上一次的处理结果上进行的。我们将多个子流程组成一个渲染流程后,Vulkan 可以对其进行一定程度的优化。
每个子流程可以引用一个或多个附着,这些引用的附着是通过 VkAttachmentReference 结构体指定的;attachment 成员变量用于指定要引用的附着在附着描述结构体数组中的索引。在这里,我们的 VkAttachmentDescription 数组只包含了一个附着信息,所以将 attachment 指定为 0 即可。layout 成员变量用于指定进行子流程时引用的附着使用的布局方式,Vulkan 会在子流程开始时自动将引用的附着转换到 layout 成员变量指定的图像布 局。我们推荐将 layout 成员变量设置为 VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,一般而言,它的性能表现最佳。
// Provided by VK_VERSION_1_0
typedef struct VkAttachmentReference {
uint32_t attachment;
VkImageLayout layout;
} VkAttachmentReference;
- attachment: VkRenderPassCreateInfo::pAttachments中对应索引处标识的附着,或者为VK_ATTACHMENT_UNUSED表示此附件未被使用。
- layout: 一个VkImageLayout值,指定附着在子传递期间使用的布局。
我们使用 VkSubpassDescription 结构体来描述子流程:
// Provided by VK_VERSION_1_0
typedef struct VkSubpassDescription {
VkSubpassDescriptionFlags flags;
VkPipelineBindPoint pipelineBindPoint;
uint32_t inputAttachmentCount;
const VkAttachmentReference* pInputAttachments;
uint32_t colorAttachmentCount;
const VkAttachmentReference* pColorAttachments;
const VkAttachmentReference* pResolveAttachments;
const VkAttachmentReference* pDepthStencilAttachment;
uint32_t preserveAttachmentCount;
const uint32_t* pPreserveAttachments;
} VkSubpassDescription;
flags:
VkSubpassDescriptionFlagBits 的位掩码,指定子流程的用法。
pipelineBindPoint:
一个VkPipelineBindPoint值,指定该子流程支持的管线类型。
inputAttachmentCount:
输入附着的数量。
pInputAttachments:
指向VkAttachmentReference 结构体数组的指针,该结构定义了此子通道的输入附着及其布局。
colorAttachmentCount:
颜色附着的数量。
pColorAttachments:
指向colorAttachmentCount大小的 VkAttachmentReference 结构体数组的指针,该结构定义了此子通道的颜色附着及其布局。
pResolveAttachments:
是NULL或指向colorAttachmentCount大小的。 VkAttachmentReference 结构体数组的指针,该结构定义了此子流程及其布局的解析附着。
pDepthStencilAttachment:
是NULL或指向colorAttachmentCount大小的 VkAttachmentReference 结构体数组的指针,该结构定义了此子流程及其布局的深度/模板附着。
preserveAttachmentCount:
保留的附件的数量。
pPreserveAttachments:
一个指向preserveAttachmentCountdax1数组的指针,表示渲染流程中的附着索引,这些索引标识此子传递不使用,但其内容必须在整个子流程中保留的附件。
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
渲染流程
现在,我们已经设置好了附着和引用它的子流程,可以开始创建渲染流程对象
// Provided by VK_VERSION_1_0
typedef struct VkRenderPassCreateInfo {
VkStructureType sType;
const void* pNext;
VkRenderPassCreateFlags flags;
uint32_t attachmentCount;
const VkAttachmentDescription* pAttachments;
uint32_t subpassCount;
const VkSubpassDescription* pSubpasses;
uint32_t dependencyCount;
const VkSubpassDependency* pDependencies;
} VkRenderPassCreateInfo;
attachmentCount:
此渲染流程使用的附着数量
pAttachments:
是一个指向 VkAttachmentDescription 结构体数组的指针,描述了渲染流程中使用的附件。
subpassCount:
要创建的子流程的数量
pSubpasses
:指向VkSubpassDescription结构体数组的指针
dependencyCount:
子流程之间的内存依赖的数量。
pDependencies:
是一个指向VkSubpassDependency 结构体数组的指针,该结构描述子通道对之间的依赖关系。
VkRenderPassCreateInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = 1;
renderPassInfo.pAttachments = &colorAttachment;
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
throw std::runtime_error("failed to create render pass!");
}
和管线布局对象一样,我们需要在应用程序结束前,清除渲染流程对象:
vkDestroyRenderPass(device, renderPass, nullptr);
渲染管线
现在,让我们回忆之下我们为了创建图形管线而创建的对象:
- 着色器阶段:定义了着色器模块用于图形管线哪一可编程阶段
- 固定功能状态:定义了图形管线的固定功能阶段使用的状态信息,比如输入装配,视口,光栅化,颜色混合
- 管线布局:定义了被着色器使用,在渲染时可以被动态修改的 uniform 变量
- 渲染流程:定义了被管线使用的附着的用途
所有这些组合完全定义了图形管线的功能,因此我们现在可以开始在createGraphicsPipeline函数的末尾填充VkGraphicsPipelineCreateInfo结构。但是在调用vkDestroyShaderModule之前,因为这些仍然要在创建期间使用。
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 = nullptr; // Optional
pipelineInfo.pColorBlendState = &colorBlending;
pipelineInfo.pDynamicState = &dynamicState;
pipelineInfo.layout = pipelineLayout;
pipelineInfo.renderPass = renderPass;
pipelineInfo.subpass = 0;
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optional
pipelineInfo.basePipelineIndex = -1; // Optional
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <exception>
#include <iostream>
#include <fstream>
#include <vector>
#include <optional>
#include <set>
#include <algorithm>
#define WIDTH 800
#define HEIGHT 600
#if NDEBUG
const bool enableValidationLayers = false;
#else
const bool enableValidationLayers = true;
#endif // enableValidationLayers
const std::vector<const char*> validationLayers = {
"VK_LAYER_KHRONOS_validation"
};
const std::vector<const char*> deviceExtensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME
};
VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pCallback) {
auto func = (PFN_vkCreateDebugUtilsMessengerEXT)
vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
if (func != nullptr) {
return func(instance, pCreateInfo, pAllocator, pCallback);
}
else {
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
}
void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) {
auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
if (func != nullptr) {
func(instance, debugMessenger, pAllocator);
}
}
struct QueueFamiliyIndices {
std::optional<uint32_t> graphicsFamily;
std::optional<uint32_t> presentFamily;
bool isCompletion() {
return graphicsFamily.has_value() && presentFamily.has_value();
}
};
struct SwapChainSupportDetails {
VkSurfaceCapabilitiesKHR capabilities;
std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> presentModes;
};
const std::vector<float> 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 HelloVulkanApplication {
public:
void run() {
initWindow();
initVulkan();
mainLoop();
cleanup();
}
private:
GLFWwindow* window;
VkInstance instance;
VkSurfaceKHR surface;
VkDebugUtilsMessengerEXT debugMessenger;
VkPhysicalDevice physicalDevice;
VkDevice device;
VkQueue graphicsQueue;
VkQueue presentQueue;
VkSwapchainKHR swapChain;
std::vector<VkImage> swapChainImages;
VkFormat swapChainImageFormat;
VkExtent2D swapChainExtent;
std::vector<VkImageView> swapChainImageViews;
VkRenderPass renderPass;
VkPipelineLayout pipelineLayout;
VkPipeline graphicsPipeline;
VkBuffer vertexBuffer;
VkDeviceMemory vertexBufferMemory;
void initWindow() {
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
window = glfwCreateWindow(WIDTH, HEIGHT, "Learn Vulkan", nullptr, nullptr);
}
void initVulkan() {
createInstance();
setupDebugMessenger(); //设置vulkan使用过程中的调试信息
createSurface();
pickPhysicalDevice();
createLogicalDevice();
createSwapChain();
createImageViews();
createRenderPass();
createGraphicsPipeline();
}
void mainLoop() {
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
}
}
void cleanup() {
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
vkDestroyRenderPass(device, renderPass, nullptr);
vkDestroyPipeline(device, graphicsPipeline, nullptr);
for (auto imageView : swapChainImageViews) {
vkDestroyImageView(device, imageView, nullptr);
}
vkDestroySwapchainKHR(device, swapChain, nullptr);
vkDestroyBuffer(device, vertexBuffer, nullptr);
vkFreeMemory(device, vertexBufferMemory, nullptr);
vkDestroyDevice(device, nullptr);
if (enableValidationLayers) {
DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
}
vkDestroySurfaceKHR(instance, surface, nullptr);
vkDestroyInstance(instance, nullptr);
glfwDestroyWindow(window);
glfwTerminate();
}
void createInstance() {
if (enableValidationLayers && !checkValidationLayerSupport()) {
throw std::runtime_error("validation layers requested, but not available");
}
VkApplicationInfo appInfo = {};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = "Hello Vulkan";
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.pEngineName = "No Engine";
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.apiVersion = VK_API_VERSION_1_0;
VkInstanceCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;
std::vector<const char*> extensions = getRequireExtensions();
createInfo.enabledExtensionCount = extensions.size();
createInfo.ppEnabledExtensionNames = extensions.data();
if (enableValidationLayers) {
createInfo.enabledLayerCount = validationLayers.size();
createInfo.ppEnabledLayerNames = validationLayers.data();
// 调试vkCreateInstance和vkDestroyInstance
VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{};
populateDebugMessengerCreateInfo(debugCreateInfo);
createInfo.pNext = &debugCreateInfo;
}
else {
createInfo.enabledLayerCount = 0;
createInfo.pNext = nullptr;
}
VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);
if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
throw std::runtime_error("Failed to create Vulkan Instance!");
}
}
std::vector<const char*> getRequireExtensions() {
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);
if (enableValidationLayers) {
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
return extensions;
}
void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
createInfo.pfnUserCallback = debugCallback;
}
void setupDebugMessenger() {
if (!enableValidationLayers) return;
VkDebugUtilsMessengerCreateInfoEXT createInfo;
populateDebugMessengerCreateInfo(createInfo);
if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
throw std::runtime_error("failed to set up debug messenger!");
}
}
bool checkValidationLayerSupport() {
uint32_t layertCount;
vkEnumerateInstanceLayerProperties(&layertCount, nullptr);
std::vector<VkLayerProperties> availabelLayers(layertCount);
vkEnumerateInstanceLayerProperties(&layertCount, availabelLayers.data());
for (auto& validationlayer : validationLayers) {
bool layerFound = false;
for (auto& availabelayer : availabelLayers) {
if (strcmp(validationlayer, availabelayer.layerName) == 0) {
layerFound = true;
break;
}
}
if (!layerFound) {
return false;
}
}
return true;
}
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) {
std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl;
return VK_FALSE;
}
void createSurface() {
if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) {
throw std::runtime_error("failed to create window surface!");
}
}
void pickPhysicalDevice() {
uint32_t deivceCount = 0;
vkEnumeratePhysicalDevices(instance, &deivceCount, 0);
std::vector<VkPhysicalDevice> Devices(deivceCount);
vkEnumeratePhysicalDevices(instance, &deivceCount, Devices.data());
for (auto& device : Devices) {
if (isDeviceSuitable(device)) {
physicalDevice = device;
break;
}
}
if (physicalDevice == VK_NULL_HANDLE) {
throw std::runtime_error("failed to find a suitable GPU!");
}
}
bool isDeviceSuitable(VkPhysicalDevice device) {
QueueFamiliyIndices indices = findQueueFamilies(device);
bool extensionSupported = checkDeviceExtensionSupport(device);
bool swapChainAdequate = false;
if (extensionSupported) {
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
}
return indices.isCompletion() && extensionSupported && swapChainAdequate;
}
QueueFamiliyIndices findQueueFamilies(VkPhysicalDevice device) {
QueueFamiliyIndices indice;
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
int i = 0;
for (const auto& queueFamily : queueFamilies) {
VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
if (presentSupport) {
indice.presentFamily = i;
}
if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT && presentSupport) {
indice.graphicsFamily = i;
}
if (indice.isCompletion()) {
break;
}
i++;
}
return indice;
}
bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
uint32_t extensionCount;
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);
std::vector<VkExtensionProperties> availableExtensions(extensionCount);
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());
std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());
for (const auto& extension : availableExtensions) {
requiredExtensions.erase(extension.extensionName);
}
return requiredExtensions.empty();
}
void createLogicalDevice() {
QueueFamiliyIndices indice = findQueueFamilies(physicalDevice);
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
std::set<uint32_t> uniqueueFamilies = { indice.graphicsFamily.value(),indice.presentFamily.value() };
float queuePriority = 1.0f;
for (uint32_t queueFamily : uniqueueFamilies) {
VkDeviceQueueCreateInfo queueCreateInfo = {};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = queueFamily;
queueCreateInfo.queueCount = 1;
queueCreateInfo.pQueuePriorities = &queuePriority;
queueCreateInfos.push_back(queueCreateInfo);
}
VkPhysicalDeviceFeatures deviceFeatures{};
VkDeviceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
createInfo.pQueueCreateInfos = queueCreateInfos.data();
createInfo.queueCreateInfoCount = 1;
createInfo.pEnabledFeatures = &deviceFeatures;
createInfo.enabledExtensionCount = deviceExtensions.size();
createInfo.ppEnabledExtensionNames = deviceExtensions.data();
if (enableValidationLayers) {
createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
createInfo.ppEnabledLayerNames = validationLayers.data();
}
else {
createInfo.enabledLayerCount = 0;
}
if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
throw std::runtime_error("failed to create logica device!");
}
vkGetDeviceQueue(device, indice.graphicsFamily.value(), 0, &graphicsQueue);
vkGetDeviceQueue(device, indice.presentFamily.value(), 0, &presentQueue);
}
SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
SwapChainSupportDetails details;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
uint32_t formatCount;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);
if (formatCount != 0) {
details.formats.resize(formatCount);
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
}
uint32_t presentModeCount;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);
if (presentModeCount != 0) {
details.presentModes.resize(presentModeCount);
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());
}
return details;
}
VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availabelFormats) {
for (const auto& availabelFormat : availabelFormats) {
if (availabelFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availabelFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
return availabelFormat;
}
}
return availabelFormats[0];
}
VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
for (const auto& availablePresentMode : availablePresentModes) {
if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
return availablePresentMode;
}
}
return VK_PRESENT_MODE_FIFO_KHR;
}
VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) {
return capabilities.currentExtent;
}
else {
int width, height;
glfwGetFramebufferSize(window, &width, &height);
VkExtent2D actualExtent = {
static_cast<uint32_t>(width),
static_cast<uint32_t>(height)
};
actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);
}
}
void createSwapChain() {
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice);
VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities);
uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
if (swapChainSupport.capabilities.minImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
imageCount = swapChainSupport.capabilities.maxImageCount;
}
VkSwapchainCreateInfoKHR createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = surface;
createInfo.minImageCount = imageCount;
createInfo.imageFormat = surfaceFormat.format;
createInfo.imageColorSpace = surfaceFormat.colorSpace;
createInfo.imageExtent = extent;
createInfo.imageArrayLayers = 1;
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
QueueFamiliyIndices indices = findQueueFamilies(physicalDevice);
uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(),indices.presentFamily.value() };
if (indices.graphicsFamily != indices.presentFamily) {
createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
createInfo.queueFamilyIndexCount = 2;
createInfo.pQueueFamilyIndices = queueFamilyIndices;
}
else {
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
createInfo.queueFamilyIndexCount = 0;
createInfo.pQueueFamilyIndices = nullptr;
}
createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE;
createInfo.oldSwapchain = VK_NULL_HANDLE;
if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
throw std::runtime_error("failed to create swap chain!");
}
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());
swapChainImageFormat = surfaceFormat.format;
swapChainExtent = extent;
}
void createImageViews() {
swapChainImageViews.resize(swapChainImages.size());
for (size_t i = 0; i < swapChainImageViews.size(); i++) {
VkImageViewCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
createInfo.image = swapChainImages[i];
createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
createInfo.format = swapChainImageFormat;
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, &swapChainImageViews[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to create image views!");
}
}
}
void createRenderPass() {
VkAttachmentDescription colorAttachment = {};
colorAttachment.format = swapChainImageFormat;
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VkAttachmentReference colorAttachmentRef = {};
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
VkRenderPassCreateInfo renderPassInfo = {};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = 1;
renderPassInfo.pAttachments = &colorAttachment;
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
throw std::runtime_error("failed to create render pass!");
}
}
void createGraphicsPipeline() {
auto vertShaderCode = readFile("shaders/vert.spv");
auto fragShaderCode = readFile("shaders/frag.spv");
VkShaderModule vertShaderModule = createShaderModule(vertShaderCode);
VkShaderModule fragShaderModule = createShaderModule(fragShaderCode);
VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertShaderStageInfo.module = vertShaderModule;
vertShaderStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragShaderStageInfo.module = fragShaderModule;
fragShaderStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo shaderStages[] = { vertShaderStageInfo, fragShaderStageInfo };
std::vector<VkDynamicState> dynamicStates = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
//动态阶段
VkPipelineDynamicStateCreateInfo dynamicState{};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
dynamicState.pDynamicStates = dynamicStates.data();
//顶点输入
VkVertexInputBindingDescription bindingDescription = {};
bindingDescription.binding = 0;
bindingDescription.stride = 5 * sizeof(float);
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
VkVertexInputAttributeDescription attributeDescriptions[2] = {};
attributeDescriptions[0].binding = 0;
attributeDescriptions[0].location = 0;
attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[0].offset = 0;
attributeDescriptions[1].binding = 0;
attributeDescriptions[1].location = 1;
attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
attributeDescriptions[1].offset = 2;
VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
vertexInputInfo.vertexAttributeDescriptionCount = 2;
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions;
//图元装配
VkPipelineInputAssemblyStateCreateInfo inputAssembly = {};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = false;
//视口
VkViewport viewport = {};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float)swapChainExtent.width;
viewport.height = (float)swapChainExtent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
//裁剪
VkRect2D scissor = {};
scissor.offset = { 0,0 };
scissor.extent = swapChainExtent;
VkPipelineViewportStateCreateInfo viewportState = {};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.pViewports = &viewport;
viewportState.scissorCount = 1;
viewportState.pScissors = &scissor;
//光栅化
VkPipelineRasterizationStateCreateInfo rasterizer = {};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthBiasClamp = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
rasterizer.lineWidth = 1.0f;
rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
rasterizer.depthBiasEnable = VK_FALSE;
rasterizer.depthBiasConstantFactor = 0.0f; // Optional
rasterizer.depthBiasClamp = 0.0f; // Optional
rasterizer.depthBiasSlopeFactor = 0.0f; // Optional
//多重采样
VkPipelineMultisampleStateCreateInfo multisampling{};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
multisampling.minSampleShading = 1.0f; // Optional
multisampling.pSampleMask = nullptr; // Optional
multisampling.alphaToCoverageEnable = VK_FALSE; // Optional
multisampling.alphaToOneEnable = VK_FALSE; // Optional
//颜色混合
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_FALSE;
colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // Optional
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; // Optional
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; // Optional
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; // Optional
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
//管线布局
VkPipelineLayoutCreateInfo pipelineLayoutInfo;
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.pNext = nullptr;
pipelineLayoutInfo.flags = VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT;
pipelineLayoutInfo.setLayoutCount = 0; // Optional
pipelineLayoutInfo.pSetLayouts = nullptr; // Optional
pipelineLayoutInfo.pushConstantRangeCount = 0; // Optional
pipelineLayoutInfo.pPushConstantRanges = nullptr; // Optional
if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) {
throw std::runtime_error("failed to create pipeline layout!");
}
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 = nullptr; // Optional
pipelineInfo.pColorBlendState = &colorBlending;
pipelineInfo.pDynamicState = &dynamicState;
pipelineInfo.layout = pipelineLayout;
pipelineInfo.renderPass = renderPass;
pipelineInfo.subpass = 0;
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optional
pipelineInfo.basePipelineIndex = -1; // Optional
if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) {
throw std::runtime_error("failed to create graphics pipeline!");
}
vkDestroyShaderModule(device, vertShaderModule, nullptr);
vkDestroyShaderModule(device, fragShaderModule, nullptr);
}
VkShaderModule createShaderModule(const std::vector<char>& code) {
VkShaderModuleCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
createInfo.codeSize = code.size();
createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());
VkShaderModule shaderModule;
if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
throw std::runtime_error("failed to create shader module!");
}
return shaderModule;
}
void createVertexBuffer() {
VkBufferCreateInfo bufferInfo{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = sizeof(float) * vertices.size();
bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (vkCreateBuffer(device, &bufferInfo, nullptr, &vertexBuffer) != VK_SUCCESS) {
throw std::runtime_error("failed to create vertex buffer!");
}
VkMemoryRequirements memRequirements;
vkGetBufferMemoryRequirements(device, vertexBuffer, &memRequirements);
VkMemoryAllocateInfo allocaInfo{};
allocaInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocaInfo.allocationSize = memRequirements.size;
allocaInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
if (vkAllocateMemory(device, &allocaInfo, nullptr, &vertexBufferMemory) != VK_SUCCESS) {
throw std::runtime_error("failed to allocate vertex buffer memory!");
}
vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0);
void* data;
vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data);
memcpy(data, vertices.data(), (size_t)bufferInfo.size);
vkUnmapMemory(device, vertexBufferMemory);
}
uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
VkPhysicalDeviceMemoryProperties memProperties;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
if (typeFilter & (1 << i) && (memProperties.memoryTypes[i].propertyFlags & properties)) {
return i;
}
}
throw std::runtime_error("failed to find suitable memory type!");
}
static std::vector<char> readFile(const std::string& filename) {
std::ifstream file(filename, std::ios::ate | std::ios::binary);
if (!file.is_open()) {
throw std::runtime_error("failed to open file!");
}
size_t fileSize = (size_t)file.tellg();
std::vector<char> buffer(fileSize);
file.seekg(0);
file.read(buffer.data(), fileSize);
file.close();
return buffer;
}
};
int main() {
HelloVulkanApplication helloVulkan;
try {
helloVulkan.run();
}
catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}