Graphics Pipeline
下图中带星号的是可编程管线。
Input Assembler
从指定的缓冲区中获取原始顶点数据,并且可以使用Index Buffer来重复使用某些元素,而不必复制顶点数据。
Vertex Shader
每个网格顶点运行一次,并且为每个顶点执行变换(Transform)操作。
Tessellation Shader
可根据某些规则细分三角形面片,以提高网格质量。
Geometry Shader
每个图元(三角形,直线,点)执行一次,并且可以丢弃或输出比引入的图元更多的图元。现在程序中使用并不多,因为除英特尔的集显外,大多数显卡Geometry Shader性能都不太好。
Rasterization
光栅化,图元离散为片段。也就是在帧缓冲区上填充像素。屏幕外部的所有片段都将被丢弃,并且顶点着色器输出的属性将在这些片段之间进行插值。 通常使用深度测试。
Fragment Shader
每个保留的片段调用一次,确定将片段写入哪个帧缓冲区,以及使用哪个颜色和深度值。 它可以使用来自Vertex Shader的插值数据来执行此操作,如纹理坐标、法线等。
Color Blending
混合映射到帧缓冲区中同一像素的不同片段。 片段可以根据透明度相互覆盖,累加或混合。
OpenGL和Direct3D等API,是通过调用glBlendFunc和OMSetBlendState更改管线设置。 Vulkan中的图形管线是完全不变的,因此如果要更改着色器,绑定不同的帧缓冲区或更改混合功能,则必须重新创建管线。 缺点是必须创建许多管线,这些管线代表要在渲染操作中使用的状态的所有不同组合。 但是,由于预先知道管线中要执行的所有操作,因此驱动程序可以对其进行更好的优化。
Shader Modules
与早期的API不同,Vulkan中的着色器代码与GLSL和HLSL不同,不使用编程语言,而是使用字节码(bytecode). 称为SPIR-V,可以同时被Vulkan和OpenCL(都是Khronos API)使用。 它是一种可用于Write Graphics和Compute Shaders的格式。使用字节码的优势是:
- 降低GPU将Shader代码转换为本机代码的编译时间。
- 由于不同GPU厂商将GLSL或HLSL转换为硬件支持的编译指令略有不用,可能导致相同的Shader代码在不用的硬件平台上出现编译错误。而使用bytecode可以避免类似的错误出现(SPIR-V)。
将GLSL或HLSL转换为SPIR-V的两种方式
-
使用Vulkan自带的glslangValidator.exe或glslc.exe
-
Windows
C:/VulkanSDK/x.x.x.x/Bin32/glslc.exe shader.vert -o vert.spv C:/VulkanSDK/x.x.x.x/Bin32/glslc.exe shader.frag -o frag.spv
-
Linux
/home/user/VulkanSDK/x.x.x.x/x86_64/bin/glslc shader.vert -o vert.spv /home/user/VulkanSDK/x.x.x.x/x86_64/bin/glslc shader.frag -o frag.spv
-
-
Vulkan SDK包含libshaderc库,这是一个可以在程序内将GLSL代码转化为SPIR-V的库.
加载Shader
shader的基础和OpenGL GLSL相同,不做笔记。
#include <fstream>
//ate: 从文件末尾开始阅读
//binary: 读取文件为二进制文件 (避免文字转换)
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!");
}
//ate的优势是,可以获取文件的大小
size_t fileSize = (size_t)file.tellg();
std::vector<char> buffer(fileSize);
//指针跳到头
file.seekg(0);
file.read(buffer.data(), fileSize);
file.close();
return buffer;
}
.............................................................................
auto vertShaderCode = readFile("shaders/vert.spv");
auto fragShaderCode = readFile("shaders/frag.spv");
Creating Shader Modules
加载完Shader后,创建 Shader Module
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!");
}
Shader Stage Creation
auto vertShaderCode = readFile("shaders/vert.spv");
auto fragShaderCode = readFile("shaders/frag.spv");
VkShaderModule vertShaderModule = createShaderModule(vertShaderCode);
VkShaderModule fragShaderModule = createShaderModule(fragShaderCode);
//可以将多个着色器组合到一个ShaderModule中,并使用不同的entry points来区分它们的行为。
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";
//指定着色器常量的值。
//单个着色器模块,在管道中创建常量,给予不同的值来配置其行为。
//比在渲染时使用变量配置着色器更为有效, 编译器可以进行优化,例如消除依赖于这些值的if语句
//如果没有这样的常量,则可以将成员设置为nullptr,初始化会自动执行该操作。
//pSpecializationInfo
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 };
//在创建图形管线之前,不会将SPIR-V字节码编译并链接到机器代码以供GPU执行。
//这意味着在管道创建完成后,就可以立即销毁ShaderModule
vkDestroyShaderModule(device, fragShaderModule, nullptr);
vkDestroyShaderModule(device, vertShaderModule, nullptr);
Fixed functions
Vertex input
VkPipelineVertexInputStateCreateInfo描述传入到顶点着色器的顶点数据的格式。 有两种描述方式:
Bindings: 描述数据之间的间距, 以及数据是per-vertex还是按per-instance
Attribute descriptions: 传入到顶点着色器的属性类型,描述其在哪个偏移处绑定加载的
VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 0;
//描述加载顶点数据的详细信息
vertexInputInfo.pVertexBindingDescriptions = nullptr;
vertexInputInfo.vertexAttributeDescriptionCount = 0;
//描述加载顶点数据的详细信息
vertexInputInfo.pVertexAttributeDescriptions = nullptr;
Input assembly
VkPipelineInputAssemblyStateCreateInfo描述两类信息:
- 绘制什么样的几何图形
- 是否启用图元重启( primitive restart)
具体值如下:
- 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
通常,vertices是按顺序从顶点缓冲区按索引加载的,但是通过元素缓冲区,您可以指定要使用的索引。 这使您可以执行诸如重用顶点之类的优化。 如果您将primaryRestartEnable成员设置为VK_TRUE,则可以通过使用特殊索引0xFFFF或0xFFFFFFFF在_STRIP拓扑模式下分解直线和三角形。
Viewports and Scissors
Viewport 描述图像渲染到帧缓冲区的区域。swap chain和images的大小可能与Viewport的宽高不同。
//viewport
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;
//viewport 和scissor可以有多个
VkPipelineViewportStateCreateInfo viewportState = {};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.pViewports = &viewport;
viewportState.scissorCount = 1;
viewportState.pScissors = &scissor;
Rasterizer
Rasterizer 从顶点着色器获取图形,并将其转换为片段,再由片段着色器着色。 它还执行深度测试,面剔除和剪裁测试,并且可以设置多边形填充还是线框渲染。
VkPipelineRasterizationStateCreateInfo rasterizer = {};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
//保留近平面和远平面之外的片元
rasterizer.depthClampEnable = VK_FALSE;
//true 几何图形不会通过光栅化阶段。 禁用对帧缓冲区的任何输出
rasterizer.rasterizerDiscardEnable = VK_FALSE;
//VK_POLYGON_MODE_FILL 用片段填充多边形区域
//VK_POLYGON_MODE_LINE 多边形边缘绘制为线
//VK_POLYGON_MODE_POINT 多边形顶点绘制为点
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
//支持的最大线宽取决于硬件,任何比1.0f粗的线都需要启用wideLines。
rasterizer.lineWidth = 1.0f;
//确定要使用的面部剔除类型。
rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
//指定面片视为正面的顶点顺序,可以是顺时针或逆时针
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
//Rasterization可以通过添加常量或基于片段的斜率对深度值进行偏置来更改深度值。
//多用于阴影贴图,如不需要将depthBiasEnable设置为VK_FALSE。
rasterizer.depthBiasEnable = VK_FALSE;
rasterizer.depthBiasConstantFactor = 0.0f;
rasterizer.depthBiasClamp = 0.0f;
rasterizer.depthBiasSlopeFactor = 0.0f;
Multisampling
MSAA与SSAA相似,在光栅化阶段,在一个像素区域内使用多个采样点,但每个像素内所有采样点共享一个着色计算,然后将颜色结果复制到每个采样点上。
//后续补充
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;
multisampling.pSampleMask = nullptr;
multisampling.alphaToCoverageEnable = VK_FALSE;
multisampling.alphaToOneEnable = VK_FALSE;
Depth and stencil testing
如果开启深度测试和模板测试,需要使用VkPipelineDepthStencilStateCreateInfo 后续补充。
Color blending
片段着色器输出颜色后,需要将其与帧缓冲区中已经存在的颜色合并。
有两种方法可以实现:
- 混合新旧值以产生最终颜色
- 使用按位运算组合新旧值
每个附加的帧缓冲区的混合规则 :
//specification详见说明文档
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;
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
//or
//VK_TRUE
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;
混合伪代码:
if (blendEnable) {
finalColor.rgb = (srcColorBlendFactor * newColor.rgb)
< colorBlendOp > (dstColorBlendFactor * oldColor.rgb);
finalColor.a = (srcAlphaBlendFactor * newColor.a) < alphaBlendOp >
(dstAlphaBlendFactor * oldColor.a);
}
else {
finalColor = newColor;
}
finalColor = finalColor & colorWriteMask;
透明混合伪代码:
finalColor.rgb = newAlpha * newColor + (1 - newAlpha) * oldColor;
finalColor.a = newAlpha.a;
全局颜色混合设置:
VkPipelineColorBlendStateCreateInfo colorBlending = {};
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
//bitwise combination 自动禁用第一种方法
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
Dynamic state
本节的所有结构体中指定的一些参数可以更改,而无需重新创建管线。 例如,视口的大小,线宽和混合常量。填写VkPipelineDynamicStateCreateInfo结构,如下所示:
//详情后续补充
VkDynamicState dynamicStates[] = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_LINE_WIDTH
};
VkPipelineDynamicStateCreateInfo dynamicState = {};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount = 2;
dynamicState.pDynamicStates = dynamicStates;
Pipeline Layout
uniform的使用,在管线创建期间,需要通过创建VkPipelineLayout对象来指定。
创建一个空的 pipeline layout:
//详情后续补充
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
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!");
}
销毁:
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
Render passes
在完成创建管线之前,需确定渲染时使用的framebuffer attachments。 指定管线有多少个color buffers和depth buffers,每个缓冲区要使用多少个samples,以及在整个渲染过程中应如何处理它们。
Attachment description
VkAttachmentDescription colorAttachment = {};
//colorAttachment的format应与swapChain图像的格式匹配
colorAttachment.format = swapChainImageFormat;
//没有使用多重采样(multisampling),将使用1个样本。
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
//clear the framebuffer to black before drawing a new frame
//VK_ATTACHMENT_LOAD_OP_LOAD 保留Attachment的现有内容
//VK_ATTACHMENT_LOAD_OP_CLEAR 开始时将值初始化为常数
//VK_ATTACHMENT_LOAD_OP_DONT_CARE 现有内容未定义; 忽略
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
//color and depth data
//VK_ATTACHMENT_STORE_OP_STORE 渲染的内容将存储在内存中,以后可以读取
//VK_ATTACHMENT_STORE_OP_DONT_CARE 渲染操作后,帧缓冲区的内容将是未定义的
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
//stencil data
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
//Textures and framebuffers
//指定在渲染开始之前图像将具有的布局。
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
//指定在渲染完成时自动过渡到的布局。
//VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL used as color attachment
//VK_IMAGE_LAYOUT_PRESENT_SRC_KHR Images the swap chain 中要显示的图像
//VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL 用作存储器复制操作目的图像
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
Subpasses and attachment references
单个render pass可以包含多个subpasses,每个subpass都引用了上一节中的结构描述的一个或多个attachments 。
//Vulkan may also support compute subpasses in the future
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
//引用colorAttachment
//数组中attachment的索引是直接从fragment shader 引用的(location = 0)out vec4 outColor指令!
//subpass可以引用以下其他类型的attachment
//pInputAttachments read from a shader
//pResolveAttachments used for multisampling attachments
//pDepthStencilAttachment depth and stencil data
//pPreserveAttachments not used by this subpass but for which the data must be preserved(保存)
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
Render pass
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);
Create The Graphics Pipeline
我们现在以创建的对象类型:
- Shader stages,定义图形管线可编程阶段功能的着色器模块
- Fixed-function state,定义管线固定功能阶段的所有结构,例如input assembly,rasterizer,viewport和color
blending - Pipeline layout,着色器引用的uniform和values referenced,可以在绘制时进行更新
- Render pass,attachment的引用以及使用规则
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 = nullptr; // Optional
pipelineInfo.layout = pipelineLayout;
//引用将使用图形管线的renderPass和subpass索引。
//也可以使用其他渲染管线,但是它们必须与renderPass兼容
pipelineInfo.renderPass = renderPass;
pipelineInfo.subpass = 0;
//Vulkan允许通过从现有管线中派生来创建新的图形管线。
//管线派生的思想是,当管线具有与现有管线共有的许多功能时,建立管线的成本较低,
//并且可以更快地完成同一父管线之间的切换。
//可以使用basePipelineHandle指定现有管线的句柄,也可以使用basePipelineIndex引用索引创建的另一个管线。
//现在只有一个管线,因此我们只需指定一个空句柄和一个无效索引。
//仅当在VkGraphicsPipelineCreateInfo的flags字段中还指定了VK_PIPELINE_CREATE_DERIVATIVE_BIT标志时,才使用这些值。
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optional
pipelineInfo.basePipelineIndex = -1; // Optional
//第二个参数VK_NULL_HANDLE引用了一个可选的VkPipelineCache对象。
//管线Cache可用于存储和重用管线创建相关的数据
//可以加快管线的创建速度,后有详解
if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) {
throw std::runtime_error("failed to create graphics pipeline!");
}
Code
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <vulkan/vulkan.h>
#include <iostream>
#include <GLFW/glfw3.h>
#include <vulkan/vulkan.h>
#include <iostream> //[1]
#include <stdexcept> //[1]异常处理函数
#include <functional>//[1]提供 EXIT_SUCCESS and EXIT_FAILURE 宏指令
#include <cstdlib>
#include <vector>
#include <map>
#include <optional>
#include <set>
#include <cstdint> // Necessary for UINT32_MAX
#include <fstream>
const int WIDTH = 800;
const int HEIGHT = 600;
//ate: Start reading at the end of the file
//binary: Read the file as binary file (avoid text transformations)
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!");
}
//ate的优势是,可以获取文件的大小
size_t fileSize = (size_t)file.tellg();
std::vector<char> buffer(fileSize);
//指针跳到头
file.seekg(0);
file.read(buffer.data(), fileSize);
file.close();
return buffer;
}
class HelloTriangleApplication
{
public:
void run()
{
initVulkan(); //[1]初始化Vulakn相关
cleanup(); //[1]释放资源
}
private:
void initVulkan()
{
//...
//[10]
createRenderPass();
createGraphicsPipeline();
}
void cleanup()
{
//[15]
vkDestroyPipeline(device, graphicsPipeline, nullptr);
//[9]
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
//[10]
vkDestroyRenderPass(device, renderPass, nullptr);
//...
}
void createGraphicsPipeline() {
//[1]
auto vertShaderCode = readFile("shaders/vert.spv");
auto fragShaderCode = readFile("shaders/frag.spv");
//[1]
VkShaderModule vertShaderModule = createShaderModule(vertShaderCode);
VkShaderModule fragShaderModule = createShaderModule(fragShaderCode);
//[1]可以将多个着色器组合到一个ShaderModule中,并使用不同的entry points来区分它们的行为。
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";
//[1]指定着色器常量的值。
//[1]单个着色器模块,在管道中创建常量,给予不同的值来配置其行为。
//[1]比在渲染时使用变量配置着色器更为有效, 编译器可以进行优化,例如消除依赖于这些值的if语句
//[1]如果没有这样的常量,则可以将成员设置为nullptr,初始化会自动执行该操作。
//[1]pSpecializationInfo
//[1]
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";
//[1]
VkPipelineShaderStageCreateInfo shaderStages[] = { vertShaderStageInfo, fragShaderStageInfo };
//[2]
VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 0;
//[2]描述上述用于加载顶点数据的详细信息
vertexInputInfo.pVertexBindingDescriptions = nullptr;
vertexInputInfo.vertexAttributeDescriptionCount = 0;
//[2]描述上述用于加载顶点数据的详细信息
vertexInputInfo.pVertexAttributeDescriptions = nullptr;
//[3]
VkPipelineInputAssemblyStateCreateInfo inputAssembly = {};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;
//[4]viewport
VkViewport viewport = {};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float)swapChainExtent.width;
viewport.height = (float)swapChainExtent.height;
//[3]minDepth和maxDepth 在0.0f到1.0f之间
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
//[3]裁剪矩形定义哪些区域像素被存储
VkRect2D scissor = {};
scissor.offset = { 0, 0 };
scissor.extent = swapChainExtent;
//[3]viewport 和scissor可以有多个
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;
//[4]保留近平面和远平面之外的片元
rasterizer.depthClampEnable = VK_FALSE;
//[4]true 几何图形不会通过光栅化阶段。 禁用对帧缓冲区的任何输出
rasterizer.rasterizerDiscardEnable = VK_FALSE;
//[4]VK_POLYGON_MODE_FILL 用片段填充多边形区域
//[4]VK_POLYGON_MODE_LINE 多边形边缘绘制为线
//[4]VK_POLYGON_MODE_POINT 多边形顶点绘制为点
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
//[4]支持的最大线宽取决于硬件,任何比1.0f粗的线都需要启用wideLines。
rasterizer.lineWidth = 1.0f;
//[4]确定要使用的面部剔除类型。
rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
//[4]指定面片视为正面的顶点顺序,可以是顺时针或逆时针
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
//[4]Rasterization可以通过添加常量或基于片段的斜率对深度值进行偏置来更改深度值。
//[4]多用于阴影贴图,如不需要将depthBiasEnable设置为VK_FALSE。
rasterizer.depthBiasEnable = VK_FALSE;
rasterizer.depthBiasConstantFactor = 0.0f;
rasterizer.depthBiasClamp = 0.0f;
rasterizer.depthBiasSlopeFactor = 0.0f;
//[5]
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
//[6]specification详见说明文档
//[6]每个附加的帧缓冲区的混合规则
VkPipelineColorBlendAttachmentState colorBlendAttachment = {};
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT |
VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT |
VK_COLOR_COMPONENT_A_BIT;
//[6]片段着色器的颜色直接输出
colorBlendAttachment.blendEnable = VK_FALSE;
colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
/*
if (blendEnable) {
finalColor.rgb = (srcColorBlendFactor * newColor.rgb)
< colorBlendOp > (dstColorBlendFactor * oldColor.rgb);
finalColor.a = (srcAlphaBlendFactor * newColor.a) < alphaBlendOp >
(dstAlphaBlendFactor * oldColor.a);
}
else {
finalColor = newColor;
}
finalColor = finalColor & colorWriteMask;
*/
//[6]透明混合
/*
finalColor.rgb = newAlpha * newColor + (1 - newAlpha) * oldColor;
finalColor.a = newAlpha.a;
*/
//[6]VK_TRUE
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;
//[6]全局颜色混合设置。
VkPipelineColorBlendStateCreateInfo colorBlending = {};
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
//[6]bitwise combination 请注意,这将自动禁用第一种方法
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
//[7]
VkDynamicState dynamicStates[] = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_LINE_WIDTH
};
//[7]
VkPipelineDynamicStateCreateInfo dynamicState = {};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount = 2;
dynamicState.pDynamicStates = dynamicStates;
//[8]
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 0; // Optional
pipelineLayoutInfo.pSetLayouts = nullptr; // Optional
pipelineLayoutInfo.pushConstantRangeCount = 0; // Optional
pipelineLayoutInfo.pPushConstantRanges = nullptr; // Optional
//[8]
if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) {
throw std::runtime_error("failed to create pipeline layout!");
}
//[13]
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 = nullptr; // Optional
pipelineInfo.layout = pipelineLayout;
//[13]引用将使用图形管线的renderPass和subpass索引。
//[13]也可以使用其他渲染管线,但是它们必须与renderPass兼容
pipelineInfo.renderPass = renderPass;
pipelineInfo.subpass = 0;
//[13]Vulkan允许通过从现有管线中派生来创建新的图形管线。
//[13]管线派生的思想是,当管线具有与现有管线共有的许多功能时,建立管线的成本较低,
//[13]并且可以更快地完成同一父管线之间的切换。
//[13]可以使用basePipelineHandle指定现有管线的句柄,也可以使用basePipelineIndex引用索引创建的另一个管线。
//[13]现在只有一个管线,因此我们只需指定一个空句柄和一个无效索引。
//[13]仅当在VkGraphicsPipelineCreateInfo的flags字段中还指定了VK_PIPELINE_CREATE_DERIVATIVE_BIT标志时,才使用这些值。
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optional
pipelineInfo.basePipelineIndex = -1; // Optional
//[14]第二个参数VK_NULL_HANDLE引用了一个可选的VkPipelineCache对象。
//[14]管线Cache可用于存储和重用管线创建相关的数据
//[14]可以加快管线的创建速度,后有详解
if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) {
throw std::runtime_error("failed to create graphics pipeline!");
}
//[1]
//在创建图形管线之前,不会将SPIR-V字节码编译并链接到机器代码以供GPU执行。
//这意味着在管道创建完成后,就可以立即销毁ShaderModule
vkDestroyShaderModule(device, fragShaderModule, nullptr);
vkDestroyShaderModule(device, vertShaderModule, nullptr);
}
//[1]
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 createRenderPass() {
VkAttachmentDescription colorAttachment = {};
//[10]colorAttachment的format应与swapChain图像的格式匹配
colorAttachment.format = swapChainImageFormat;
//[10]没有使用多重采样(multisampling),将使用1个样本。
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
//[10]clear the framebuffer to black before drawing a new frame
//[10]VK_ATTACHMENT_LOAD_OP_LOAD 保留Attachment的现有内容
//[10]VK_ATTACHMENT_LOAD_OP_CLEAR 开始时将值初始化为常数
//[10]VK_ATTACHMENT_LOAD_OP_DONT_CARE 现有内容未定义; 忽略
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
//[10]color and depth data
//[10]VK_ATTACHMENT_STORE_OP_STORE 渲染的内容将存储在内存中,以后可以读取
//[10]VK_ATTACHMENT_STORE_OP_DONT_CARE 渲染操作后,帧缓冲区的内容将是未定义的
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
//[10]stencil data
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
//[10]Textures and framebuffers
//[10]指定在渲染开始之前图像将具有的布局。
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
//[10]指定在渲染完成时自动过渡到的布局。
//[10]VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL used as color attachment
//[10]VK_IMAGE_LAYOUT_PRESENT_SRC_KHR Images the swap chain 中要显示的图像
//[10]VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL 用作存储器复制操作目的图像
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
//[11]引用Attachment
VkAttachmentReference colorAttachmentRef = {};
//[11]attachment参数通过attachment描述数组中的索引指定要引用的attachment
colorAttachmentRef.attachment = 0;
//[11]布局指定了我们希望attachment在使用此引用的subpass中具有哪种布局。
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
//[11]Vulkan may also support compute subpasses in the future
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
//[11]引用colorAttachment
//[11]数组中attachment的索引是直接从fragment shader 引用的(location = 0)out vec4 outColor指令!
//[11]subpass可以引用以下其他类型的attachment
//[11]pInputAttachments read from a shader
//[11]pResolveAttachments used for multisampling attachments
//[11]pDepthStencilAttachment depth and stencil data
//[11]pPreserveAttachments not used by this subpass but for which the data must be preserved(保存)
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
//[12]
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!");
}
}
private:
VkExtent2D swapChainExtent;
VkSwapchainKHR swapChain;
VkDevice device;
VkPipelineLayout pipelineLayout;
VkRenderPass renderPass;
VkPipeline graphicsPipeline;
};
int main() {
HelloTriangleApplication app;
//[1]捕获异常
try {
app.run();
}
catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}