目录
1. 处理窗口调整大小
当窗口大小改变时,需要重新创建交换链和相关资源。
监控窗口大小变化
我们可以使用GLFW的窗口大小回调函数来监控窗口大小变化。
void framebufferResizeCallback(GLFWwindow* window, int width, int height) {
auto app = reinterpret_cast<HelloTriangleApplication*>(glfwGetWindowUserPointer(window));
app->framebufferResized = true;
}
初始化GLFW窗口时设置回调函数
void initWindow() {
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
glfwSetWindowUserPointer(window, this);
glfwSetFramebufferSizeCallback(window, framebufferResizeCallback);
}
重新创建交换链
在主循环中,我们需要检查 framebufferResized
标志并重新创建交换链。
void mainLoop() {
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
if (framebufferResized) {
recreateSwapChain();
framebufferResized = false;
}
drawFrame();
}
vkDeviceWaitIdle(device);
}
实现重新创建交换链的逻辑
void recreateSwapChain() {
int width = 0, height = 0;
glfwGetFramebufferSize(window, &width, &height);
while (width == 0 || height == 0) {
glfwGetFramebufferSize(window, &width, &height);
glfwWaitEvents();
}
vkDeviceWaitIdle(device);
cleanupSwapChain();
createSwapChain();
createImageViews();
createRenderPass();
createGraphicsPipeline();
createFramebuffers();
createCommandBuffers();
}
void cleanupSwapChain() {
for (auto framebuffer : swapChainFramebuffers) {
vkDestroyFramebuffer(device, framebuffer, nullptr);
}
vkFreeCommandBuffers(device, commandPool, static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data());
vkDestroyPipeline(device, graphicsPipeline, nullptr);
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
vkDestroyRenderPass(device, renderPass, nullptr);
for (auto imageView : swapChainImageViews) {
vkDestroyImageView(device, imageView, nullptr);
}
vkDestroySwapchainKHR(device, swapChain, nullptr);
}
- recreateSwapChain 函数负责重新创建交换链及相关资源。
- cleanupSwapChain 函数负责清理旧的交换链资源。
2. 性能优化
Vulkan提供了许多性能优化的机会,以下是一些常见的优化技巧。
1. 使用合适的同步机制
确保适当地使用信号量和栅栏来同步GPU操作,避免不必要的等待。
2. 优化资源分配
减少资源分配和内存分配的频率。例如,可以预分配足够大的命令池和描述符池。
3. 使用命令缓冲重用
尽量重用命令缓冲,而不是每帧重新分配和记录命令缓冲。
void createCommandBuffers() {
if (commandBuffers.size() > 0) {
vkFreeCommandBuffers(device, commandPool, static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data());
}
commandBuffers.resize(swapChainFramebuffers.size());
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.commandPool = commandPool;
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!");
}
for (size_t i = 0; i < commandBuffers.size(); i++) {
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) {
throw std::runtime_error("failed to begin recording command buffer!");
}
VkRenderPassBeginInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = renderPass;
renderPassInfo.framebuffer = swapChainFramebuffers[i];
renderPassInfo.renderArea.offset = {0, 0};
renderPassInfo.renderArea.extent = swapChainExtent;
VkClearValue clearColor = {0.0f, 0.0f, 0.0f, 1.0f};
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &clearColor;
vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
VkBuffer vertexBuffers[] = {vertexBuffer};
VkDeviceSize offsets[] = {0};
vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets);
vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16);
vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
vkCmdDrawIndexed(commandBuffers[i], static_cast<uint32_t>(indices.size()), 1, 0, 0, 0);
vkCmdEndRenderPass(commandBuffers[i]);
if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to record command buffer!");
}
}
}
4. 管线状态对象的预编译
在Vulkan中,图形管线是不可变的。在应用启动时尽可能地预编译所有必要的图形管线对象,可以减少在运行时的开销。
5. 使用批处理
尽量将多个绘制命令合并到一个命令缓冲中,以减少提交到GPU的命令数量。
6. 内存管理
选择合适的内存类型,并确保使用合适的内存属性(如 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
),以确保资源在GPU端高效存储和访问。
7. 着色器优化
在编写着色器时尽量优化代码,减少不必要的计算和内存访问。使用SPIR-V优化工具优化着色器字节码。
结论
通过以上的教程,我们已经详细介绍了如何使用Vulkan进行初始化、资源管理、图形管线设置、描述符集配置、绘制和呈现,以及如何处理窗口调整大小和性能优化。接下来我们探讨一些高级主题,包括多线程渲染、计算着色器和Vulkan中的光线追踪。