Framebuffers
在render pass创建期间指定attachments ,并绑定到VkFramebuffer对象。 framebuffer引用所有VkImageView 对象。 使用检索到swap chain返回的需展示的图像作为图像attachments。 这意味着我们必须为swap chain中的所有图像创建一个framebuffer,并在绘制时使用检索到的图像对应的framebuffer。
//调整容器大小以容纳所有帧缓冲区
swapChainFramebuffers.resize(swapChainImageViews.size());
//create framebuffers
for (size_t i = 0; i < swapChainImageViews.size(); i++) {
VkImageView attachments[] = { swapChainImageViews[i] };
VkFramebufferCreateInfo framebufferInfo = {};
framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebufferInfo.renderPass = renderPass;
framebufferInfo.attachmentCount = 1;
framebufferInfo.pAttachments = attachments;
framebufferInfo.width = swapChainExtent.width;
framebufferInfo.height = swapChainExtent.height;
framebufferInfo.layers = 1;
if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to create framebuffer!");
}
}
Command buffers
Vulkan 中的命令,如绘图操作和内存传输,不是直接使用函数调用执行。 而是在命令缓冲区对象中记录要执行的所有操作。 这样做的好处是,所有设置绘图命令的繁重工作都可以提前在子线程中(多线程)完成。 之后,你只需要告诉 Vulkan 执行主循环中的命令。
Command pools
在创建command buffers前,先创建Command pools, Command pools管理用于存储buffers的内存,并从中分配command buffers。
QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice);
VkCommandPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value();
//There are two possible flags for command pools
//VK_COMMAND_POOL_CREATE_TRANSIENT_BIT 提示CommandPool经常用新命令重新记录(可能会改变内存分配行为)
//VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT 允许单独重新记录命CommandPool,否则都必须一起重置
poolInfo.flags = 0; //不使用flag
if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) {
throw std::runtime_error("failed to create command pool!");
}
Command buffer allocation
我们必须为swap chain 中的每个图像记录一个command buffe。
VkCommandBufferAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.commandPool = commandPool;
//指定primary 或 secondary command buffers.
//VK_COMMAND_BUFFER_LEVEL_PRIMARY 可以提交到队列执行,但不能从其他命令缓冲区调用。
//VK_COMMAND_BUFFER_LEVEL_SECONDARY 不能直接提交,但可以从主命令缓冲区调用
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandBufferCount = (uint32_t)commandBuffers.size();
if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) {
throw std::runtime_error("failed to allocate command buffers!");
}
Starting command buffer recording
//recording a command buffer
for (size_t i = 0; i < commandBuffers.size(); i++) {
VkCommandBufferBeginInfo beginInfo = {};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
//指定指定我们将如何使用command buffers.
//VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT 在执行一次后立即rerecord。
//VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT 这是一个辅助command buffers, 将在单个渲染通道中使用
//VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT command buffer 可以在已经挂起执行时重新提交。
beginInfo.flags = 0; // Optional
//仅与secondary command buffers 相关。 它指定从primarycommand buffers 继承哪些状态。
beginInfo.pInheritanceInfo = nullptr; // Optional
if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) {
throw std::runtime_error("failed to begin recording command buffer!");
}
}
Starting a render pass
//Starting a render pass
VkRenderPassBeginInfo renderPassInfo = {};
//the render pass itself and the attachments to bind
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = renderPass;
renderPassInfo.framebuffer = swapChainFramebuffers[i];
//the size of the render area
renderPassInfo.renderArea.offset = { 0, 0 }; //It should match the size of the attachments for best performance
renderPassInfo.renderArea.extent = swapChainExtent;
VkClearValue clearColor = { 0.0f, 0.0f, 0.0f, 1.0f };
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &clearColor;
//最后一个参数:how the drawing commands within the render pass will be provided
//VK_SUBPASS_CONTENTS_INLINE render pass commands 将嵌入 primary command buffer, 并且不会执行 secondary command buffers
//VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERSrender pass commands 将从 secondary command buffers 执行
vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
Basic drawing commands & Finishing up
//[6]Basic drawing commands
//[6]第二个参数指定管线对象是图形管线还是计算管线。
vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
//[6]vertexCount 即使没有顶点缓冲区,从技术上讲,仍然有3个顶点要绘制
//[6]instanceCount 用于实例化渲染,如果不进行实例化使用1
//[6]firstVertex 顶点缓冲区的偏移量,定义gl_VertexIndex的最小值
//[6]firstInstance 用作实例渲染的偏移量,定义gl_InstanceIndex的最小值
vkCmdDraw(commandBuffers[i], 3, 1, 0, 0);
//[7]The render pass can now be ended
if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to record command buffer!");
}
Rendering and presentation
Synchronization
drawFrame 函数将执行以下操作:
- 从swap chain中获取图像
- 使用在 framebuffer 中的 image 作为 attachment,执行 command buffer
- 将图像返回到swap chain进行展示
drawFrame 是异步执行的。 而且未定义执行顺序。但是每一个操作都依赖于前一个完成。 有两种同步swap chain事件的方法:fences 和 semaphores。 它们都是可用于协调操作的对象,方法是让一个操作信号和另一个操作等待fences 或semaphores从无信号状态变为有信号状态。 不同之处在于,可以使用如 vkWaitForFences 之类的调用从程序中访问fences 的状态,而semaphores则不能。fences 主要用于同步应用程序本身与渲染操作,而semaphores用于同步命令队列内或跨命令队列的操作。 我们希望同步绘制命令和显示的队列操作,emaphores最适合。
Semaphores
//Vulkan API 会扩展 flags 和 pNext 参数。
VkSemaphoreCreateInfo semaphoreInfo = {};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS ||
vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS) {
throw std::runtime_error("failed to create semaphores!");
}
-------------------------------------
vkDestroySemaphore(device, renderFinishedSemaphore, nullptr);
vkDestroySemaphore(device, imageAvailableSemaphore, nullptr);
Acquiring an image from the swap chain
uint32_t imageIndex;
//第三个参数指定可获得图像的超时时间(以纳秒为单位)。
//接下来的两个参数指定了同步对象,这些对象将在引擎使用完图像时发出信号。 可以指定semaphore、fence或都指定
//用于输出可用swap chain中图像的索引。
vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex);
Submitting the command buffer
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
VkSemaphore waitSemaphores[] = { imageAvailableSemaphore };
VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
//前三个参数指定在执行开始之前等待哪些Semaphore,以及在管线的哪个阶段等待。
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
submitInfo.pWaitDstStageMask = waitStages;
//指定实际提交执行的commandBuffer。
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffers[imageIndex];
//指定在commandBuffer完成执行后发送信号的Semaphore。
VkSemaphore signalSemaphores[] = { renderFinishedSemaphore };
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
//最后一个参数引用了可选的 fenc, 当 command buffer 执行完成时,它将发出信号。
//我们使用信号量进行同步,所以我们传递VK_NULL_HANDLE。
if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) {
throw std::runtime_error("failed to submit draw command buffer!");
}
Subpass dependencies
有两个内置依赖项负责在渲染过程开始时和渲染过程结束时的过渡,但前者不会在正确的时间发生。它假设转换发生在管线的开始,但是我们还没有获得图像!有两种方法来处理这个问题。我们可以将imageAvailableSemaphore的waitStages更改为VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT ,以确保渲染过程在图像可用之前不会开始,或者我们可以使渲染过程等待VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT 阶段。这里使用第二个选项,因为这是查看子通道依赖项及其工作方式的最佳方式。
//前两个字段指定依赖项和依赖subpass的索引
VkSubpassDependency dependency = {};
//根据在渲染过程之前或之后引用subpass, 决定是否在srcsubass或dstsubass中指定。
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
//The index 0 refers to our subpass
//The dstSubpass must always be higher than srcSubpass,unless one of the subpasses is VK_SUBPASS_EXTERNAL
dependency.dstSubpass = 0;
//指定操作发生的阶段
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.srcAccessMask = 0;
//wait on this are in the color attachment stage
//involve the reading and writing of the color attachment
//These settings will prevent the transition from happening until it’s actually necessary
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
Presentation
VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
//在呈现(Present)之前等待哪些信号量
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = signalSemaphores;
VkSwapchainKHR swapChains[] = { swapChain };
presentInfo.swapchainCount = 1;
//为每个swapChain指定呈现的图像和图像索引
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = &imageIndex;
//check for every individual swap chain if presentation was successful. (array of VkResult)
presentInfo.pResults = nullptr; // Optional
//submits the request to present an image to the swap chain.
vkQueuePresentKHR(presentQueue, &presentInfo);
-------------------------
//同步执行方法.
vkDeviceWaitIdle(device);
Frames in flight
虽然现在已经设置了所需的对象以便于同时处理多个帧,但实际上我们仍然没有阻止提交超过MAX_FRAMES_IN_FLIGHT的帧。现在只有GPU-GPU同步,没有CPU-GPU同步来跟踪工作进展。程序可能正在使用 frame #0对象,而 frame #0仍在运行中!为了执行CPU-GPU同步,Vulkan提供了第二种称为fences的msynchronization原语。fence与semaphores类似,因为它们可以发出信号并等待
代码为[13]
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;
struct QueueFamilyIndices {
std::optional<uint32_t> graphicsFamily;
std::optional<uint32_t> presentFamily;
//[8]为了方便起见,我们还将向结构本身添加一个泛型检查
bool isComplete() {
return graphicsFamily.has_value() && presentFamily.has_value();
}
};
//[12]
const int MAX_FRAMES_IN_FLIGHT = 2;
class HelloTriangleApplication
{
public:
void run()
{
initVulkan(); //[1]初始化Vulakn相关
//[8]
mainLoop();
cleanup(); //[1]释放资源
}
private:
void initVulkan()
{
//...
createRenderPass();
createGraphicsPipeline();
//[1]
createFramebuffers();
//[2]
createCommandPool();
//[3]
createCommandBuffers();
//[9]
createSemaphores();
}
void cleanup()
{
//[9]
//vkDestroySemaphore(device, renderFinishedSemaphore, nullptr);
//vkDestroySemaphore(device, imageAvailableSemaphore, nullptr);
// or
//[12]
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr);
vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr);
//[13]
vkDestroyFence(device, inFlightFences[i], nullptr);
}
//[2]
vkDestroyCommandPool(device, commandPool, nullptr);
//[1]
for (auto framebuffer : swapChainFramebuffers) {
vkDestroyFramebuffer(device, framebuffer, nullptr);
}
vkDestroyPipeline(device, graphicsPipeline, nullptr);
//...
}
void createGraphicsPipeline() {
}
QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
QueueFamilyIndices indices;
return indices;
}
void createFramebuffers()
{
//[1]调整容器大小以容纳所有帧缓冲区
swapChainFramebuffers.resize(swapChainImageViews.size());
//[1] create framebuffers
for (size_t i = 0; i < swapChainImageViews.size(); i++) {
VkImageView attachments[] = { swapChainImageViews[i] };
VkFramebufferCreateInfo framebufferInfo = {};
framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebufferInfo.renderPass = renderPass;
framebufferInfo.attachmentCount = 1;
framebufferInfo.pAttachments = attachments;
framebufferInfo.width = swapChainExtent.width;
framebufferInfo.height = swapChainExtent.height;
framebufferInfo.layers = 1;
if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to create framebuffer!");
}
}
}
void createCommandPool() {
QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice);
VkCommandPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value();
//[2]There are two possible flags for command pools
//[2]VK_COMMAND_POOL_CREATE_TRANSIENT_BIT 提示CommandPool经常用新命令重新记录(可能会改变内存分配行为)
//[2]VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT 允许单独重新记录命CommandPool,否则都必须一起重置
poolInfo.flags = 0; //不使用flag
if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) {
throw std::runtime_error("failed to create command pool!");
}
}
void createCommandBuffers() {
commandBuffers.resize(swapChainFramebuffers.size());
VkCommandBufferAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.commandPool = commandPool;
//[3]指定primary 或 secondary command buffers.
//[3]VK_COMMAND_BUFFER_LEVEL_PRIMARY 可以提交到队列执行,但不能从其他命令缓冲区调用。
//[3]VK_COMMAND_BUFFER_LEVEL_SECONDARY 不能直接提交,但可以从主命令缓冲区调用
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandBufferCount = (uint32_t)commandBuffers.size();
if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) {
throw std::runtime_error("failed to allocate command buffers!");
}
//[4]recording a command buffer
for (size_t i = 0; i < commandBuffers.size(); i++) {
VkCommandBufferBeginInfo beginInfo = {};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
//[4]指定指定我们将如何使用command buffers.
//[4]VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT 在执行一次后立即rerecord。
//[4]VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT 这是一个辅助command buffers, 将在单个渲染通道中使用
//[4] VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT command buffer 可以在已经挂起执行时重新提交。
beginInfo.flags = 0; // Optional
//[4]仅与secondary command buffers 相关。 它指定从primarycommand buffers 继承哪些状态。
beginInfo.pInheritanceInfo = nullptr; // Optional
if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) {
throw std::runtime_error("failed to begin recording command buffer!");
}
//[5]Starting a render pass
VkRenderPassBeginInfo renderPassInfo = {};
//[5]the render pass itself and the attachments to bind
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = renderPass;
renderPassInfo.framebuffer = swapChainFramebuffers[i];
//[5]the size of the render area
renderPassInfo.renderArea.offset = { 0, 0 }; //It should match the size of the attachments for best performance
renderPassInfo.renderArea.extent = swapChainExtent;
VkClearValue clearColor = { 0.0f, 0.0f, 0.0f, 1.0f };
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &clearColor;
//[5]最后一个参数:how the drawing commands within the render pass will be provided
//[5]VK_SUBPASS_CONTENTS_INLINE render pass commands 将嵌入 primary command buffer, 并且不会执行 secondary command buffers
//[5]VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERSrender pass commands 将从 secondary command buffers 执行
vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
//[6]Basic drawing commands
//[6]第二个参数指定管线对象是图形管线还是计算管线。
vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
//[6]vertexCount 即使没有顶点缓冲区,从技术上讲,仍然有3个顶点要绘制
//[6]instanceCount 用于实例化渲染,如果不进行实例化使用1
//[6]firstVertex 顶点缓冲区的偏移量,定义gl_VertexIndex的最小值
//[6]firstInstance 用作实例渲染的偏移量,定义gl_InstanceIndex的最小值
vkCmdDraw(commandBuffers[i], 3, 1, 0, 0);
//[7]The render pass can now be ended
if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to record command buffer!");
}
}
}
void mainLoop() {
while (!glfwWindowShouldClose(window))
{
glfwPollEvents();
//[8]
drawFrame();
}
//[12]可以用作执行同步的基本的方法.
vkDeviceWaitIdle(device);
}
void createRenderPass() {
VkAttachmentDescription colorAttachment = {};
VkAttachmentReference colorAttachmentRef = {};
VkSubpassDescription subpass = {};
VkRenderPassCreateInfo renderPassInfo = {};
//[10]前两个字段指定依赖项和依赖subpass的索引
VkSubpassDependency dependency = {};
//[10]根据在渲染过程之前或之后引用subpass, 决定是否在srcsubass或dstsubass中指定。
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
//[10]The index 0 refers to our subpass
//[10]The dstSubpass must always be higher than srcSubpass,unless one of the subpasses is VK_SUBPASS_EXTERNAL
dependency.dstSubpass = 0;
//[10]指定操作发生的阶段
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.srcAccessMask = 0;
//[10]wait on this are in the color attachment stage
//[10]involve the reading and writing of the color attachment
//[10]These settings will prevent the transition from happening until it’s actually necessary
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
renderPassInfo.dependencyCount = 1;
renderPassInfo.pDependencies = &dependency;
if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS)
{
throw std::runtime_error("failed to create render pass!");
}
}
//[8]异步执行的
//[8]acquire an image from the swap chain
void drawFrame() {
//[13]wait for the frame to be finished
//[13]vkWaitForFences函数接受一个fences数组,并等待其中任何一个或所有fences在返回之前发出信号。
//[13]这里传递的VK_TRUE表示要等待所有的fences,但是对于单个fences来说,这显然无关紧要。
//[13]就像vkAcquireNextImageKHR一样,这个函数也需要超时。与信号量不同,我们需要通过vkresetfines调用将fence重置为unsignaled状态。
//vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
//or
uint32_t imageIndex;
//[8]第三个参数指定可获得图像的超时时间(以纳秒为单位)。
//[8]接下来的两个参数指定了同步对象,这些对象将在引擎使用完图像时发出信号。 可以指定semaphore、fence或都指定
//[8]用于输出可用swap chain中图像的索引。
//vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex);
//or
//[12]
vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
//[13]Check if a previous frame is using this image (i.e. there is its fence to wait on)
if (imagesInFlight[imageIndex] != VK_NULL_HANDLE) {
vkWaitForFences(device, 1, &imagesInFlight[imageIndex], VK_TRUE, UINT64_MAX);
}
//[13] Mark the image as now being in use by this frame
imagesInFlight[imageIndex] = inFlightFences[currentFrame];
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
//VkSemaphore waitSemaphores[] = { imageAvailableSemaphore };
//or
//[12]
VkSemaphore waitSemaphores[] = { imageAvailableSemaphores[currentFrame] };
VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
//[9]前三个参数指定在执行开始之前等待哪些Semaphore,以及在管线的哪个阶段等待。
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
submitInfo.pWaitDstStageMask = waitStages;
//[9]指定实际提交执行的commandBuffer。
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffers[imageIndex];
//[9]指定在commandBuffer完成执行后发送信号的Semaphore。
//VkSemaphore signalSemaphores[] = { renderFinishedSemaphore};
//or
//[12]
VkSemaphore signalSemaphores[] = { renderFinishedSemaphores[currentFrame] };
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
//[13]
vkResetFences(device, 1, &inFlightFences[currentFrame]);
//[9]最后一个参数引用了可选的 fenc, 当 command buffer 执行完成时,它将发出信号。
//[9]我们使用信号量进行同步,所以我们传递VK_NULL_HANDLE。
//if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) {
// throw std::runtime_error("failed to submit draw command buffer!");
//}
//or
//[13]
if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) {
throw std::runtime_error("failed to submit draw command buffer!");
}
VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
//[11]在呈现(Present)之前等待哪些信号量
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = signalSemaphores;
VkSwapchainKHR swapChains[] = { swapChain };
presentInfo.swapchainCount = 1;
//[11]为每个swapChain指定呈现的图像和图像索引
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = &imageIndex;
//[11]check for every individual swap chain if presentation was successful. (array of VkResult)
presentInfo.pResults = nullptr; // Optional
//[11]submits the request to present an image to the swap chain.
vkQueuePresentKHR(presentQueue, &presentInfo);
//[12]
vkQueueWaitIdle(presentQueue);
//[12]
currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
}
//[13]renamed createSemaphores to createSyncObjects :
void createSemaphores() {
//[12]
imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
//[13]
inFlightFences.resize(MAX_FRAMES_IN_FLIGHT);
imagesInFlight.resize(swapChainImages.size(), VK_NULL_HANDLE);
//[9] Vulkan API 会扩展 flags 和 pNext 参数。
VkSemaphoreCreateInfo semaphoreInfo = {};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
//[13]
VkFenceCreateInfo fenceInfo = {};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
//[13]if we had rendered an initial frame that finished
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
//[12]
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
//[9]
if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS ||
vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS ||
//[13]
vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to create semaphores for a frame!");
}
}
}
private:
GLFWwindow* window;
VkDevice device;
VkSwapchainKHR swapChain;
VkExtent2D swapChainExtent;
VkRenderPass renderPass;
VkPipeline graphicsPipeline;
VkQueue graphicsQueue;
std::vector<VkImage> swapChainImages;
VkQueue presentQueue;
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
std::vector<VkImageView> swapChainImageViews;
//[1]
std::vector<VkFramebuffer> swapChainFramebuffers;
//[2]
VkCommandPool commandPool;
//[3] Command buffers will be automatically freed when their command pool is destroyed
std::vector<VkCommandBuffer> commandBuffers;
//[9]
VkSemaphore imageAvailableSemaphore;
VkSemaphore renderFinishedSemaphore;
//[12]
std::vector<VkSemaphore> imageAvailableSemaphores;
std::vector<VkSemaphore> renderFinishedSemaphores;
//[12]
size_t currentFrame = 0;
//[13]
std::vector<VkFence> inFlightFences;
std::vector<VkFence> imagesInFlight;
};
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;
}