vulkan笔记

sumcai 2020.10.08

vulkan笔记

准备工作

先编译好spirv shader文件:

1
2
D:\program\VulkanSDK\1.2.198.1\Bin\glslc.exe triangle.vert -o triangle.vert.spv
D:\program\VulkanSDK\1.2.198.1\Bin\glslc.exe triangle.frag -o triangle.frag.spv

cmake中将shader目录拷贝到执行目录

1
file(COPY shaders/ DESTINATION shaders)

实例

实例是应用程序和vulkan库之间的桥梁,第一步需创建实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 创建实例,选择显卡版本, spec1.3 p72
auto vkApiVersion = VK_API_VERSION_1_0;
// vkEnumerateInstanceVersion返回null表示1.0
if (glfwGetInstanceProcAddress(nullptr, "vkEnumerateInstanceVersion") != nullptr) {
	vkApiVersion = vk::enumerateInstanceVersion();
}
vk::ApplicationInfo appInfo("Hello Triangle", VK_MAKE_VERSION(1, 0, 0), "No Engine",
	VK_MAKE_VERSION(1, 0, 0), vkApiVersion);

auto glfwExtensionCount = 0u;
auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
std::vector<const char*> glfwExtensionsVector(glfwExtensions, glfwExtensions + glfwExtensionCount);
auto layers = std::vector<const char*>{ "VK_LAYER_KHRONOS_validation" };
vk::InstanceCreateInfo instanceCreateInfo({}, &appInfo, static_cast<uint32_t>(layers.size()),
	layers.data(), static_cast<uint32_t>(glfwExtensionsVector.size()), glfwExtensionsVector.data());

auto instance = vk::createInstance(instanceCreateInfo);

// 创建窗口平面
VkSurfaceKHR surfaceKhr;
glfwCreateWindowSurface(*instance, window, nullptr, &surfaceKhr);
vk::UniqueSurfaceKHR surface(surfaceKhr, *instance);

设备和队列

Vulkan中的几乎所有操作,从绘制到上传纹理,都需要将命令提交到队列,因此需要根据instance选择所需功能的设备和队列。队列随逻辑设备创建时自动创建,随逻辑设备的销毁而自动销毁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 2.2 查询图形队列、显示队列索引
auto queueFamilyProperties = physicalDevice.getQueueFamilyProperties();
auto graphicsQueueFamilyIndex = std::distance(queueFamilyProperties.begin(),
	std::find_if(queueFamilyProperties.begin(), queueFamilyProperties.end(),
		[](vk::QueueFamilyProperties& properties) {
			return properties.queueFlags & vk::QueueFlagBits::eGraphics;
		}));

VkSurfaceKHR surfaceKhr;
glfwCreateWindowSurface(*instance, window, nullptr, &surfaceKhr);
vk::UniqueSurfaceKHR surface(surfaceKhr, *instance);

auto presentQueueFamilyIndex = 0;
for (auto i = 0; i < queueFamilyProperties.size(); i++) {
	if (physicalDevice.getSurfaceSupportKHR(i, surface.get())) {
		presentQueueFamilyIndex = i;
	}
}

// 2.3 创建逻辑设备
std::set<uint32_t> uniqueQueueFamilyIndices = { static_cast<uint32_t>(graphicsQueueFamilyIndex),
												static_cast<uint32_t>(presentQueueFamilyIndex) };
std::vector<vk::DeviceQueueCreateInfo> queueCreateInfo;
float queuePriority = 0.0f;
for (auto &queueFamilyIndex : uniqueQueueFamilyIndices) {
	queueCreateInfo.push_back(vk::DeviceQueueCreateInfo {vk::DeviceQueueCreateFlags(), queueFamilyIndex, 1, &queuePriority});
}
const std::vector<const char*> deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
vk::DeviceCreateInfo deviceCreateInfo(vk::DeviceCreateFlags(), queueCreateInfo.size(), queueCreateInfo.data(),
	0, nullptr, deviceExtensions.size(), deviceExtensions.data());
auto device = physicalDevice.createDeviceUnique(deviceCreateInfo);

// 获取图形、显示队列
auto graphicsQueue = device->getQueue(graphicsQueueFamilyIndex, 0);
auto presentQueue = device->getQueue(presentQueueFamilyIndex, 0);

交换链

vulkan没有”默认缓冲区”的概念,因此在渲染的内容呈现到屏幕前需要将内容存储在一个缓冲区中,拥有该缓冲区的结构称为交换链,它必须在vulkan中显示创建。交换链本质上是一个等待呈现到屏幕的图像队列,应用程序从交换链中获取图像缓冲用来绘制内容,然后将其返回到队列,交换链中图像的呈现与屏幕的刷新率保持同步。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 3. 创建交换链
auto sharingMode = vk::SharingMode::eExclusive;
uint32_t familyIndicesCount = 0;
uint32_t * familyIndicesDataPtr = nullptr;
if (graphicsQueueFamilyIndex != presentQueueFamilyIndex) {
	sharingMode = vk::SharingMode::eConcurrent;
	familyIndicesCount = 2;
	familyIndicesDataPtr = std::vector<uint32_t>{uniqueQueueFamilyIndices.begin(), uniqueQueueFamilyIndices.end()}.data();
}

uint32_t imageCount = 2;
auto format = vk::Format::eB8G8R8A8Unorm;
auto extent = vk::Extent2D{ width, height };
vk::SwapchainCreateInfoKHR swapChainCreateInfo({}, surface.get(), imageCount, format,
	vk::ColorSpaceKHR::eSrgbNonlinear, extent, 1, vk::ImageUsageFlagBits::eColorAttachment,
	sharingMode, familyIndicesCount, familyIndicesDataPtr, vk::SurfaceTransformFlagBitsKHR::eIdentity,
	vk::CompositeAlphaFlagBitsKHR::eOpaque, vk::PresentModeKHR::eFifo, true, nullptr);
auto swapChain = device->createSwapchainKHRUnique(swapChainCreateInfo);

// 获取swapchain images
std::vector<vk::Image> swapChainImages = device->getSwapchainImagesKHR(swapChain.get());
// 创建swapchain的imageview
std::vector<vk::UniqueImageView> imageViews;
for (auto& image : swapChainImages) {
	vk::ImageViewCreateInfo imageViewCreateInfo(vk::ImageViewCreateFlags(), image,vk::ImageViewType::e2D, format,vk::ComponentMapping{ vk::ComponentSwizzle::eR, vk::ComponentSwizzle::eG,
			vk::ComponentSwizzle::eB, vk::ComponentSwizzle::eA },vk::ImageSubresourceRange{ vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 });
	imageViews.push_back(device->createImageViewUnique(imageViewCreateInfo));
}

渲染管线

渲染管线如图所示:

img

绿色的阶段被称为固定流水线。这个阶段允许使用自定义的参数数值,但是它内部的工作逻辑是预制好的。橙色的阶段被称为可编程阶段programmable,我们可以向GPU提交自己编写的代码执行具体的逻辑。创建渲染管线的主要工作就是创建上面每一步操作并设置到管线上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 4. 创建渲染管线
// 4.1 创建着色器
// vs
auto vertexShaderCode = ReadFile("shaders/triangle.vert.spv");
vk::ShaderModuleCreateInfo vertexShaderCreateInfo({}, vertexShaderCode.size(), reinterpret_cast<const uint32_t *>(vertexShaderCode.data()));
auto vertexShaderMoudle = device->createShaderModuleUnique(vertexShaderCreateInfo);
vk::PipelineShaderStageCreateInfo vertexShaderStageInfo({}, vk::ShaderStageFlagBits::eVertex, vertexShaderMoudle.get(), "main");
// fs
auto fragmentShaderCode = ReadFile("shaders/triangle.frag.spv");
vk::ShaderModuleCreateInfo fragmentShaderCreateInfo({}, fragmentShaderCode.size(), reinterpret_cast<const uint32_t *>(fragmentShaderCode.data()));
auto fragmentShaderMoudle = device->createShaderModuleUnique(fragmentShaderCreateInfo);
vk::PipelineShaderStageCreateInfo fragmentShaderStageInfo({}, vk::ShaderStageFlagBits::eFragment, fragmentShaderMoudle.get(), "main");
// 4.2 创建自定义着色器阶段
auto pipelineShaderStages = std::vector<vk::PipelineShaderStageCreateInfo>{ vertexShaderStageInfo, fragmentShaderStageInfo };
// 4.3 设置顶点输入
vk::PipelineVertexInputStateCreateInfo vertexInputInfo({}, 0u, nullptr, 0u, nullptr);
// 4.4 图元装配
vk::PipelineInputAssemblyStateCreateInfo inputAssembly({}, vk::PrimitiveTopology::eTriangleList, false);
// 4.5 设置viewport和裁剪
vk::Viewport viewport(0.0f, 0.0f, width, height, 0.0f, 1.0f);
vk::Rect2D scissor({ 0, 0 }, extent);
vk::PipelineViewportStateCreateInfo viewportState({}, 1, &viewport, 1, &scissor);
// 4.6 设置光栅化阶段状态数据
vk::PipelineRasterizationStateCreateInfo rasterizer({}, false, false, vk::PolygonMode::eFill,
	{}, vk::FrontFace::eCounterClockwise, {}, {}, {}, {}, 1.0f);
// 4.7 设置多重采样状态数据
vk::PipelineMultisampleStateCreateInfo multisampling({}, vk::SampleCountFlagBits::e1, false, 1.0);
// 4.8 设置颜色混合因子等数据
vk::PipelineColorBlendAttachmentState colorBlendAttachment({}, vk::BlendFactor::eOne, vk::BlendFactor::eZero,
	vk::BlendOp::eAdd, vk::BlendFactor::eOne, vk::BlendFactor::eZero, vk::BlendOp::eAdd,
	vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA);
vk::PipelineColorBlendStateCreateInfo colorBlending({}, false,vk::LogicOp::eCopy, 1, &colorBlendAttachment);
// 4.9 创建管线布局
auto pipelineLayout = device->createPipelineLayoutUnique({});
// 4.10 创建renderpass
// 创建renderpass的附件数据
vk::AttachmentDescription colorAttachment({}, format, vk::SampleCountFlagBits::e1,vk::AttachmentLoadOp::eClear, vk::AttachmentStoreOp::eStore, {}, {}, {}, vk::ImageLayout::ePresentSrcKHR);
// vulkan的rendrpass需要至少有一个subpass保存输出
vk::AttachmentReference colourAttachmentRef(0, vk::ImageLayout::eColorAttachmentOptimal);
// 绑定到layout=0的输出
vk::SubpassDescription subpass({}, vk::PipelineBindPoint::eGraphics, 0, nullptr, 1, &colourAttachmentRef);
// 创建renderpass, 因为只有一个renderpass, 所以可以不用管SubpassDependency
vk::RenderPassCreateInfo renderPassCreateInfo({}, 1, &colorAttachment, 1, &subpass);
auto renderPass = device->createRenderPassUnique(renderPassCreateInfo);
// 4.11 创建渲染管线
vk::GraphicsPipelineCreateInfo pipelineCreateInfo({}, 2, pipelineShaderStages.data(),
	&vertexInputInfo, &inputAssembly, nullptr, &viewportState, &rasterizer, &multisampling,
	nullptr, &colorBlending, nullptr, *pipelineLayout, *renderPass, 0);
auto pipeline = device->createGraphicsPipelineUnique({}, pipelineCreateInfo).value;

帧缓冲

vulkan没有默认帧缓冲,需要自己创建帧缓冲

1
2
3
4
5
6
7
// 5. 创建framebuffer
std::vector<vk::UniqueFramebuffer> frameBuffers;
frameBuffers.resize(imageCount);
for (int i = 0; i < imageViews.size(); ++i) {
	vk::FramebufferCreateInfo framebufferCreateInfo({}, *renderPass, 1, &(*imageViews[i]), extent.width, extent.height, 1);
	frameBuffers[i] = device->createFramebufferUnique(framebufferCreateInfo);
}

命令缓冲

为每个帧缓冲录制绘制指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 6. 录制绘制指令
// 6.1 创建commnadbuffer pool
vk::CommandPoolCreateInfo commandPoolCreateInfo({}, graphicsQueueFamilyIndex);
auto commandPoolUnique = device->createCommandPoolUnique(commandPoolCreateInfo);
// 6.2 从commnadbuffer pool中分配command buffer
vk::CommandBufferAllocateInfo commandBufferAllocateInfo(commandPoolUnique.get(), vk::CommandBufferLevel::ePrimary, frameBuffers.size());
std::vector<vk::UniqueCommandBuffer> commandBuffers = device->allocateCommandBuffersUnique(commandBufferAllocateInfo);
// 6.3 录制每个commandbuffer的绘制指令
for (int i = 0; i < commandBuffers.size(); i++) {
	vk::CommandBufferBeginInfo commandBufferBeginInfo = {};
	commandBuffers[i]->begin(commandBufferBeginInfo);
	vk::ClearValue clearValue = {};
	vk::RenderPassBeginInfo renderPassBeginInfo(renderPass.get(), frameBuffers[i].get(), vk::Rect2D{ { 0, 0 }, extent}, 1, &clearValue);
	commandBuffers[i]->beginRenderPass(renderPassBeginInfo, vk::SubpassContents::eInline);
	commandBuffers[i]->bindPipeline(vk::PipelineBindPoint::eGraphics, *pipeline);
	commandBuffers[i]->draw(3, 1, 0, 0);
	commandBuffers[i]->endRenderPass();
	commandBuffers[i]->end();
}

渲染呈现

渲染呈现的循环流程:

  1. 等待上一帧完成
  2. 从交换链中获取一个待绘制图像
  3. 准备绘制该图像的命令缓冲
  4. 提交命令缓冲
  5. 展示交换链上的图像

需要严格按照上面的顺序执行,这里增加了两个信号量,保证获取图像、提交队列、图像呈现的顺序

acquire swapchain image ==ready==> submit queue ==ready==> present image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 7. 渲染呈现
vk::SemaphoreCreateInfo semaphoreCreateInfo = {};
auto imageAvailableSemaphore = device->createSemaphoreUnique(semaphoreCreateInfo);
auto renderFinishedSemaphore = device->createSemaphoreUnique(semaphoreCreateInfo);
while (!glfwWindowShouldClose(window)) {
	glfwPollEvents();

	// 7.1 获取swapchain中image的索引
	auto imageIndex = device->acquireNextImageKHR(swapChain.get(), std::numeric_limits<uint64_t>::max(), imageAvailableSemaphore.get(), {});

	// 7.2 提交到图形队列
	vk::PipelineStageFlags waitStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
	vk::SubmitInfo submitInfo(1, &imageAvailableSemaphore.get(), &waitStageMask, 1, &commandBuffers[imageIndex.value].get(), 1, &renderFinishedSemaphore.get());
	graphicsQueue.submit(submitInfo);

	// 7.3 呈现渲染结果图像
	vk::PresentInfoKHR presentInfoKhr(1, &renderFinishedSemaphore.get(), 1, &swapChain.get(), &imageIndex.value);
	auto result = presentQueue.presentKHR(presentInfoKhr);

	// 7.4 等待队列执行完成
	device->waitIdle();
}