OpenGL / OpenCL 编程指南 / 第 15 章:Vulkan 入门
第 15 章:Vulkan 入门
Vulkan 是 Khronos 推出的下一代跨平台图形和计算 API。它提供了对 GPU 更底层的控制,同时带来了更高的多线程效率和更低的 CPU 开销。本章为 OpenGL 开发者快速建立 Vulkan 的概念框架。
15.1 Vulkan vs OpenGL
15.1.1 设计哲学对比
| 维度 | OpenGL | Vulkan |
|---|---|---|
| 驱动模型 | 驱动负责大部分工作 | 应用显式管理一切 |
| 状态管理 | 全局状态机 | 通过 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 对应 | 说明 |
|---|---|---|
| Instance | 无 | Vulkan 实例,全局上下文 |
| Physical Device | 无 | GPU 硬件信息查询 |
| Device | Context | 逻辑设备 |
| Queue | 无 | 命令执行队列 |
| Command Buffer | 无 | 预录制的 GPU 命令 |
| Swapchain | 无 | 管理屏幕图像 |
| Render Pass | FBO | 渲染通道描述 |
| Pipeline | 着色器程序 + 状态 | 完整渲染管线 |
| Descriptor Set | Uniform | 着色器资源绑定 |
| Buffer | VBO/UBO/SSBO | GPU 缓冲 |
| Image | Texture | GPU 图像 |
| Semaphore | 无 | 队列间同步 |
| Fence | 无 | CPU-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 同步原语
| 原语 | 作用 | 粒度 |
|---|---|---|
| Fence | CPU 等待 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 Allocator | GPU 内存分配器 |
| 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 理解会深入很多