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

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 理解会深入很多

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