强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

OpenGL / OpenCL 编程指南 / 第 15 章:Vulkan 入门

第 15 章:Vulkan 入门

Vulkan 是 Khronos 推出的下一代跨平台图形和计算 API。它提供了对 GPU 更底层的控制,同时带来了更高的多线程效率和更低的 CPU 开销。本章为 OpenGL 开发者快速建立 Vulkan 的概念框架。


15.1 Vulkan vs OpenGL

15.1.1 设计哲学对比

维度OpenGLVulkan
驱动模型驱动负责大部分工作应用显式管理一切
状态管理全局状态机通过 Pipeline 对象封装
多线程单上下文,多线程困难原生多线程命令录制
错误检查默认启用需手动启用 Validation Layer
内存管理驱动隐式管理应用显式分配/释放
着色器运行时 GLSL 编译离线编译 SPIR-V
CPU 开销较高极低

15.1.2 代码量对比

绘制一个三角形:

API大约代码行数
OpenGL~100 行
Vulkan~800-1000 行

💡 Vulkan 的代码量大是因为它不隐藏任何细节。理解 Vulkan 有助于深入理解 GPU 的实际工作方式。


15.2 Vulkan 核心概念

15.2.1 对象层次

Instance (实例)
  └── Physical Device (物理设备)
        └── Device (逻辑设备)
              ├── Queue (队列)
              ├── Command Pool (命令池)
              │     └── Command Buffer (命令缓冲)
              ├── Swapchain (交换链)
              │     └── Image (图像)
              ├── Render Pass (渲染通道)
              ├── Pipeline (管线)
              │     ├── Graphics Pipeline (图形管线)
              │     └── Compute Pipeline (计算管线)
              ├── Descriptor Set (描述符集)
              └── Memory (设备内存)

15.2.2 Vulkan 对象速查

Vulkan 对象OpenGL 对应说明
InstanceVulkan 实例,全局上下文
Physical DeviceGPU 硬件信息查询
DeviceContext逻辑设备
Queue命令执行队列
Command Buffer预录制的 GPU 命令
Swapchain管理屏幕图像
Render PassFBO渲染通道描述
Pipeline着色器程序 + 状态完整渲染管线
Descriptor SetUniform着色器资源绑定
BufferVBO/UBO/SSBOGPU 缓冲
ImageTextureGPU 图像
Semaphore队列间同步
FenceCPU-GPU 同步

15.3 初始化流程

15.3.1 代码概览(C++ 伪代码)

// 1. 创建 Instance
VkInstanceCreateInfo instanceInfo{};
instanceInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instanceInfo.enabledLayerCount = validationLayers.size();
instanceInfo.ppEnabledLayerNames = validationLayers.data();
vkCreateInstance(&instanceInfo, nullptr, &instance);

// 2. 选择物理设备
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
std::vector<VkPhysicalDevice> devices(deviceCount);
vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
// 选择满足需求的设备...

// 3. 创建逻辑设备和队列
VkDeviceCreateInfo deviceInfo{};
// ... 配置队列族、扩展等
vkCreateDevice(physicalDevice, &deviceInfo, nullptr, &device);

// 4. 获取队列
vkGetDeviceQueue(device, graphicsFamily, 0, &graphicsQueue);
vkGetDeviceQueue(device, presentFamily, 0, &presentQueue);

// 5. 创建交换链
VkSwapchainCreateInfoKHR swapInfo{};
// ... 配置表面格式、呈现模式等
vkCreateSwapchainKHR(device, &swapInfo, nullptr, &swapchain);

// 6. 创建图像视图
// 7. 创建渲染通道
// 8. 创建帧缓冲
// 9. 创建命令池和命令缓冲
// 10. 创建同步对象
// 11. 创建图形管线

15.4 命令缓冲(Command Buffer)

15.4.1 命令缓冲模型

命令池 (Command Pool)
  └── 命令缓冲 A [录制: 绑定管线 → 绑定顶点 → 绘制]
  └── 命令缓冲 B [录制: 绑定管线 → 绑定顶点 → 绘制]
  └── 命令缓冲 C [录制: 计算分派]

提交到队列:
Queue: [CmdBuf A] → [CmdBuf B] → [CmdBuf C]

15.4.2 录制命令缓冲

// 开始录制
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
vkBeginCommandBuffer(commandBuffer, &beginInfo);

// 开始渲染通道
VkRenderPassBeginInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = renderPass;
renderPassInfo.framebuffer = framebuffer;
renderPassInfo.renderArea.offset = {0, 0};
renderPassInfo.renderArea.extent = swapChainExtent;

VkClearValue clearColor = {{{0.1f, 0.1f, 0.2f, 1.0f}}};
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &clearColor;

vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);

// 绑定管线
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);

// 绑定顶点缓冲
VkBuffer vertexBuffers[] = {vertexBuffer};
VkDeviceSize offsets[] = {0};
vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);

// 绑定索引缓冲
vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32);

// 绑定描述符集
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
                        pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);

// 绘制
vkCmdDrawIndexed(commandBuffer, indexCount, 1, 0, 0, 0);

// 结束渲染通道
vkCmdEndRenderPass(commandBuffer);

// 结束录制
vkEndCommandBuffer(commandBuffer);

15.5 同步机制

15.5.1 同步原语

原语作用粒度
FenceCPU 等待 GPU命令缓冲级别
Semaphore队列间同步队列提交级别
Pipeline Barrier命令间同步命令级别
Event细粒度同步命令内部

15.5.2 渲染帧的同步

void drawFrame() {
    // 1. 等待上一帧完成
    vkWaitForFences(device, 1, &inFlightFence, VK_TRUE, UINT64_MAX);
    vkResetFences(device, 1, &inFlightFence);

    // 2. 获取交换链图像
    uint32_t imageIndex;
    vkAcquireNextImageKHR(device, swapchain, UINT64_MAX,
                          imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex);

    // 3. 重置并录制命令缓冲
    vkResetCommandBuffer(commandBuffer, 0);
    recordCommandBuffer(commandBuffer, imageIndex);

    // 4. 提交命令
    VkSubmitInfo submitInfo{};
    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;

    VkSemaphore waitSemaphores[] = {imageAvailableSemaphore};
    VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
    submitInfo.waitSemaphoreCount = 1;
    submitInfo.pWaitSemaphores = waitSemaphores;
    submitInfo.pWaitDstStageMask = waitStages;

    submitInfo.commandBufferCount = 1;
    submitInfo.pCommandBuffers = &commandBuffer;

    VkSemaphore signalSemaphores[] = {renderFinishedSemaphore};
    submitInfo.signalSemaphoreCount = 1;
    submitInfo.pSignalSemaphores = signalSemaphores;

    vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence);

    // 5. 呈现
    VkPresentInfoKHR presentInfo{};
    presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
    presentInfo.waitSemaphoreCount = 1;
    presentInfo.pWaitSemaphores = signalSemaphores;
    presentInfo.swapchainCount = 1;
    presentInfo.pSwapchains = &swapchain;
    presentInfo.pImageIndices = &imageIndex;
    vkQueuePresentKHR(presentQueue, &presentInfo);
}
帧同步时序:

GPU:  [imageAvailable] → [渲染命令] → [renderFinished]
                                         ↓
CPU:  [等待上一帧] → [录制命令] → [提交] → [等待下个图像]
                                         ↓
显示:                                  [呈现]

15.6 图形管线

15.6.1 Vulkan 管线是不可变的

Vulkan 图形管线(固定阶段顺序):

顶点输入 → 顶点着色器 → 细分控制 → 细分求值 → 几何着色器
    → 光栅化 → 片段着色器 → 颜色混合 → 帧缓冲输出

每个阶段的配置在管线创建时一次性确定,运行时不可更改。

15.6.2 管线创建

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 = &depthStencil;
pipelineInfo.pColorBlendState = &colorBlending;
pipelineInfo.layout = pipelineLayout;
pipelineInfo.renderPass = renderPass;
pipelineInfo.subpass = 0;

vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline);

15.7 SPIR-V 中间表示

15.7.1 什么是 SPIR-V?

SPIR-V 是着色器的二进制中间表示。Vulkan 不接受 GLSL 源码,需要预编译:

GLSL 源码 → glslangValidator → SPIR-V 二进制 → Vulkan 加载
# 编译 GLSL 到 SPIR-V
glslangValidator -V shader.vert -o vert.spv
glslangValidator -V shader.frag -o frag.spv

# 验证 SPIR-V
spirv-val vert.spv

15.7.2 Vulkan GLSL 与 OpenGL GLSL 差异

// Vulkan GLSL 着色器
#version 450

// 使用 layout(set=X, binding=X) 代替 OpenGL 的 layout(binding=X)
layout(set = 0, binding = 0) uniform UniformBufferObject {
    mat4 model;
    mat4 view;
    mat4 projection;
} ubo;

layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aColor;
layout(location = 0) out vec3 fragColor;

void main() {
    gl_Position = ubo.projection * ubo.view * ubo.model * vec4(aPos, 1.0);
    fragColor = aColor;
}

15.8 学习路线建议

15.8.1 从 OpenGL 到 Vulkan 的迁移路径

阶段内容时间
1理解 Vulkan 概念模型(本章)1 周
2完成 Vulkan Tutorial(vulkan-tutorial.com)2-3 周
3实现基础渲染引擎1-2 月
4学习高级特性(多线程、GPU 驱动渲染)持续

15.8.2 推荐框架

框架说明
vk-bootstrap简化 Vulkan 初始化
Vulkan Memory AllocatorGPU 内存分配器
Dear ImGui (Vulkan backend)调试 UI

15.9 注意事项

⚠️ Vulkan Validation Layer 必须开启:Vulkan 默认不检查错误,错误使用会导致未定义行为。开发阶段务必启用 VK_LAYER_KHRONOS_validation

⚠️ 内存管理:Vulkan 要求应用手动管理 GPU 内存。忘记释放会导致资源泄漏,错误使用会导致崩溃。

⚠️ 同步是你的责任:忘记信号量或屏障会导致数据竞争和画面撕裂。

⚠️ 设备兼容性:不是所有设备都支持所有 Vulkan 特性。必须查询 vkGetPhysicalDeviceFeatures


15.10 业务场景

场景 1:AAA 游戏引擎

Vulkan 的多线程命令录制和低 CPU 开销使其成为现代游戏引擎的首选。

场景 2:VR/AR 应用

Vulkan 的低延迟和显式控制非常适合 VR 的严格帧率要求。

场景 3:跨平台渲染后端

Unity、Unreal 等引擎通过 Vulkan 后端实现跨 PC/主机/移动端的统一渲染。


15.11 扩展阅读

资源说明
Vulkan Tutorial最佳入门教程
Vulkan Specification官方规范
vkspec.dev在线版规范,可搜索
Vulkan Samples官方示例
Khronos Vulkan Guide官方指南

本章小结

  • Vulkan 提供对 GPU 的底层控制,CPU 开销远低于 OpenGL
  • 核心概念:Instance → Device → Queue → CommandBuffer → Pipeline
  • 命令缓冲预录制后批量提交,支持多线程并行录制
  • 同步通过 Fence(CPU-GPU)、Semaphore(队列间)和 Barrier(命令间)实现
  • 图形管线在创建时固定,运行时不可变
  • 着色器使用 SPIR-V 二进制格式,需离线编译
  • Vulkan 学习曲线陡峭,但掌握后对 GPU 理解会深入很多

上一章第 14 章:OpenGL ES 与 WebGL 下一章第 16 章:Docker 中的 GPU