Vulkan 教程第四部分:窗口调整大小与性能优化

目录

1. 处理窗口调整大小

监控窗口大小变化

初始化GLFW窗口时设置回调函数

重新创建交换链

实现重新创建交换链的逻辑

2. 性能优化

1. 使用合适的同步机制

2. 优化资源分配

3. 使用命令缓冲重用

4. 管线状态对象的预编译

5. 使用批处理

6. 内存管理

7. 着色器优化

结论


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中的光线追踪。

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值