4
社区成员
发帖
与我相关
我的任务
分享┌─────────────────────────────────────────────────────────────────────────────┐ │ Vulkan 初始化 13 步 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 阶段一:基础设施 (1-5) │ │ │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ │ │ │ │ ① createInstance() —— 建立与Vulkan驱动的连接 │ │ │ │ ↓ │ │ │ │ ② setupDebugMessenger() —— 安装错误检测系统 (仅调试) │ │ │ │ ↓ │ │ │ │ ③ createSurface() —— 创建渲染目标窗口 │ │ │ │ ↓ │ │ │ │ ④ pickPhysicalDevice() —— 选择GPU硬件 │ │ │ │ ↓ │ │ │ │ ⑤ createLogicalDevice() —— 打开与GPU的软件通信通道 │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 阶段二:渲染目标 (6-7) │ │ │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ │ │ │ │ ⑥ createSwapChain() —— 创建显示图像缓冲区 │ │ │ │ ↓ │ │ │ │ ⑦ createImageViews() —— 让GPU可以写入图像 │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 阶段三:渲染配置 (8-10) │ │ │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ │ │ │ │ ⑧ createRenderPass() —— 定义"怎么画"的步骤 │ │ │ │ ↓ │ │ │ │ ⑨ createGraphicsPipeline()—— 配置完整的渲染流水线 │ │ │ │ ↓ │ │ │ │ ⑩ createFramebuffers() —— 将"怎么画"绑定到"画在哪里" │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 阶段四:命令准备 (11-13) │ │ │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ │ │ │ │ ⑪ createCommandPool() —— 创建命令内存分配器 │ │ │ │ ↓ │ │ │ │ ⑫ createCommandBuffers() —— 预分配命令存储空间 │ │ │ │ ↓ │ │ │ │ ⑬ createSyncObjects() —— 创建CPU-GPU同步机制 │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐ │ 类比:渲染餐厅 13 步 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ① createInstance() = 办理营业执照 │ │ 建立与Vulkan驱动的连接 │ │ │ │ ② setupDebugMessenger() = 安装监控摄像头 │ │ 安装错误检测系统 │ │ │ │ ③ createSurface() = 租用店面 │ │ 创建窗口表面 │ │ │ │ ④ pickPhysicalDevice() = 选择厨房设备品牌 │ │ 选择合适的GPU │ │ │ │ ⑤ createLogicalDevice() = 签订厨房使用合同 │ │ 创建逻辑设备 │ │ │ │ ⑥ createSwapChain() = 购买餐具(盘子) │ │ 创建交换链图像 │ │ │ │ ⑦ createImageViews() = 将餐具摆上餐台 │ │ 为图像创建视图 │ │ │ │ ⑧ createRenderPass() = 制定出菜流程 │ │ 定义渲染操作结构 │ │ │ │ ⑨ createGraphicsPipeline() = 培训厨师团队 │ │ 配置完整的渲染流水线 │ │ │ │ ⑩ createFramebuffers() = 给每个盘子分配对应的菜 │ │ 绑定渲染目标和帧缓冲 │ │ │ │ ⑪ createCommandPool() = 采购记事本 │ │ 创建命令内存分配器 │ │ │ │ ⑫ createCommandBuffers() = 在记事本上写菜谱 │ │ 分配命令缓冲区 │ │ │ │ ⑬ createSyncObjects() = 制定上菜顺序和时间 │ │ 创建CPU-GPU同步机制 │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐ │ 步骤依赖关系图 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ① Instance ──────────┬─────► ② DebugMessenger │ │ │ │ │ │ ├─────► ③ Surface ──┬──► ④ PhysicalDevice ──► ⑤ Device │ │ │ │ │ │ │ └───────────────────┘ │ │ │ │ │ │ ▼ │ │ ⑥ SwapChain │ │ │ │ │ ▼ │ │ ⑦ ImageViews │ │ │ │ │ ▼ │ │ ⑧ RenderPass ──┬──► ⑨ Pipeline │ │ │ │ │ │ ▼ ▼ │ │ ⑩ Framebuffers │ │ │ │ │ ▼ │ │ ⑪ CommandPool │ │ │ │ │ ▼ │ │ ⑫ CommandBuffers │ │ │ │ │ ▼ │ │ ⑬ SyncObjects │ │ │ │ 依赖规则:箭头指向的步骤必须在其之前创建 │ │ 例如:⑥ SwapChain 需要 ⑤ Device 已经存在 │ │ ⑩ Framebuffers 需要 ⑦ ImageViews 和 ⑧ RenderPass 都存在 │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
| 步骤 | 核心问题 | 如果不做会怎样? |
|---|---|---|
| ① Instance | "我要用Vulkan,谁在管理?" | 没有Vulkan驱动连接,无法进行任何操作 |
| ② Debug | "出错了谁能告诉我?" | 验证层消息无法接收,调试困难 |
| ③ Surface | "图像要画到哪里?" | 没有显示目标,无法呈现 |
| ④ PhysicalDevice | "用哪块GPU?" | 不知道该用哪个显卡 |
| ⑤ Device | "如何与GPU通信?" | 没有操作接口,无法提交命令 |
| ⑥ SwapChain | "用多少个缓冲区?" | 没有帧缓冲区管理,画面闪烁 |
| ⑦ ImageViews | "如何让GPU写入图像?" | GPU无法直接操作交换链图像 |
| ⑧ RenderPass | "渲染流程是什么?" | 不知道帧缓冲的加载/存储方式 |
| ⑨ Pipeline | "用什么着色器?如何光栅化?" | 没有渲染状态,无法绘制三角形 |
| ⑩ Framebuffers | "具体画到哪个图像上?" | 渲染不知道输出目标 |
| ⑪ CommandPool | "命令分配在哪?" | 无法管理命令缓冲区内存 |
| ⑫ CommandBuffers | "命令序列存哪?" | 每一帧都要重新分配,效率低 |
| ⑬ SyncObjects | "CPU怎么知道GPU完成了?" | CPU和GPU不同步,出现数据竞争 |
┌─────────────────────────────────────────────────────────────────────────────┐ │ 帧循环数据流 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 初始化阶段创建的资源: │ │ │ │ ⑬ SyncObjects ──► ⑫ CommandBuffer ──► ⑤ Device ──► ④ PhysicalDevice │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ │ │ 等待上一帧完成 vkCmdDraw(...) vkQueueSubmit GPU执行 │ │ │ │ │ ▼ │ │ ⑨ Pipeline │ │ │ │ │ ▼ │ │ ⑩ Framebuffer ──► ⑦ ImageView ──► ⑥ SwapChain │ │ │ │ │ ▼ │ │ ⑧ RenderPass │ │ │ │ │ ▼ │ │ 呈现到屏幕 │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
// OpenGL风格 (隐含大量状态) glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLES, 0, 3); // 驱动做了大量幕后工作,你不知道发生了什么 // Vulkan风格 (显式控制每一步) // 1. 我需要先建立连接 (Instance) // 2. 选择硬件 (PhysicalDevice) // 3. 建立通信 (Device) // 4. 配置目标 (SwapChain + Framebuffers) // 5. 配置渲染方式 (RenderPass + Pipeline) // 6. 准备命令 (CommandBuffer) // 7. 同步执行 (SyncObjects) // 每一步你都清楚发生了什么,并且可以优化
Vulkan的设计哲学就是: 让你精确控制每一步,消除驱动的"魔法",代价就是需要更多的初始化代码。这13步是Vulkan程序能够高效运行的基础架构!
┌─────────────────────────────────────────────────────────────────────────────────┐ │ Vulkan 完整渲染过程 │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ 阶段1: CPU准备 (每帧开始) │ │ ════════════════════════════════════════════════════════════════════════════ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ ① vkWaitForFences() │ │ │ │ ├─► CPU等待当前帧的栅栏被触发 │ │ │ │ ├─► 栅栏触发 = 上一帧的GPU工作已完成 │ │ │ │ └─► CPU确认GPU可用,开始新帧 │ │ │ └─────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ ② vkAcquireNextImageKHR() │ │ │ │ ├─► CPU向GPU请求下一个可用的交换链图像 │ │ │ │ ├─► GPU检查显示控制器状态 │ │ │ │ ├─► GPU返回可用的图像索引 │ │ │ │ └─► 图像获取完成后触发 imageAvailableSemaphore │ │ │ └─────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ ③ vkResetFences() │ │ │ │ ├─► CPU重置当前帧的栅栏为未触发状态 │ │ │ │ └─► 准备用于本次提交 │ │ │ └─────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ ④ vkResetCommandBuffer() + recordCommandBuffer() │ │ │ │ ├─► CPU重置命令缓冲区 │ │ │ │ ├─► CPU开始录制命令 (实际是写入内存) │ │ │ │ │ │ │ │ │ │ 录制的命令序列: │ │ │ │ │ ├─► vkCmdBeginRenderPass() │ │ │ │ │ │ ├─► 设置渲染区域 │ │ │ │ │ │ ├─► 绑定帧缓冲 │ │ │ │ │ │ └─► 设置清除颜色 (清空为黑色) │ │ │ │ │ ├─► vkCmdBindPipeline() │ │ │ │ │ │ └─► 绑定图形管线 (着色器+固定功能状态) │ │ │ │ │ ├─► vkCmdSetViewport() │ │ │ │ │ │ └─► 设置视口大小 │ │ │ │ │ ├─► vkCmdSetScissor() │ │ │ │ │ │ └─► 设置裁剪矩形 │ │ │ │ │ ├─► vkCmdDraw() │ │ │ │ │ │ ├─► 告诉GPU画3个顶点 │ │ │ │ │ │ ├─► 1个实例 │ │ │ │ │ │ └─► 从顶点0开始 │ │ │ │ │ └─► vkCmdEndRenderPass() │ │ │ │ │ └─► 结束渲染 │ │ │ │ └─► vkEndCommandBuffer() 结束录制 │ │ │ └─────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ ⑤ vkQueueSubmit() │ │ │ │ ├─► CPU构建提交信息 │ │ │ │ ├─► 设置等待: imageAvailableSemaphore │ │ │ │ ├─► 设置命令: commandBuffers[currentFrame] │ │ │ │ ├─► 设置触发: renderFinishedSemaphore │ │ │ │ ├─► 设置栅栏: inFlightFences[currentFrame] │ │ │ │ ├─► CPU→GPU: 将命令缓冲区传输到GPU可访问内存 │ │ │ │ └─► GPU调度器接收命令 │ │ │ └─────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ 阶段2: GPU执行 (异步进行) │ │ ════════════════════════════════════════════════════════════════════════════ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ ⑥ GPU调度器处理 │ │ │ │ ├─► 等待 imageAvailableSemaphore 被触发 │ │ │ │ ├─► 检查命令缓冲区的依赖 │ │ │ │ └─► 开始执行命令序列 │ │ │ └─────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ ⑦ vkCmdBeginRenderPass() - GPU执行 │ │ │ │ ├─► GPU准备帧缓冲 (目标图像) │ │ │ │ ├─► GPU执行清除操作 (填充为黑色) │ │ │ │ └─► GPU进入渲染状态 │ │ │ └─────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ ⑧ 图形管线执行 (每个顶点/像素) │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ 输入汇编器 (Input Assembler) │ │ │ │ │ │ ├─► 读取顶点数据 (从顶点缓冲区) │ │ │ │ │ │ ├─► 组装图元 (3个顶点组成三角形) │ │ │ │ │ │ └─► 输出顶点流 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ 顶点着色器 (Vertex Shader) - GPU核心执行 │ │ │ │ │ │ ├─► 输入: 顶点位置 (x, y, z) │ │ │ │ │ │ ├─► 处理: 模型变换、视图变换、投影变换 │ │ │ │ │ │ ├─► 输出: 裁剪空间位置 (x, y, z, w) │ │ │ │ │ │ └─► 输出: 其他数据 (法线、纹理坐标、颜色) │ │ │ │ │ │ │ │ │ │ │ │ 例子: 3个顶点 │ │ │ │ │ │ v0: (-0.5, -0.5, 0.0) → ( -0.5, -0.5, 0.0, 1.0 ) │ │ │ │ │ │ v1: ( 0.5, -0.5, 0.0) → ( 0.5, -0.5, 0.0, 1.0 ) │ │ │ │ │ │ v2: ( 0.0, 0.5, 0.0) → ( 0.0, 0.5, 0.0, 1.0 ) │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ 细分/几何着色器 (可选) - 本例跳过 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ 光栅化 (Rasterization) - GPU硬件单元 │ │ │ │ │ │ ├─► 将三角形转换为片元 (像素) │ │ │ │ │ │ ├─► 计算片元覆盖范围 │ │ │ │ │ │ ├─► 插值顶点属性 (颜色、纹理坐标等) │ │ │ │ │ │ └─► 生成片元流 │ │ │ │ │ │ │ │ │ │ │ │ 例子: 三角形覆盖了约5000个像素 │ │ │ │ │ │ 每个片元包含: 位置、插值的颜色、深度值 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ 片段着色器 (Fragment Shader) - GPU核心执行 │ │ │ │ │ │ ├─► 输入: 插值的顶点数据 │ │ │ │ │ │ ├─► 处理: 计算每个像素的颜色 │ │ │ │ │ │ ├─► 可以: 纹理采样、光照计算、阴影计算 │ │ │ │ │ │ └─► 输出: 颜色值 (R, G, B, A) │ │ │ │ │ │ │ │ │ │ │ │ 例子: 三角形颜色从红到蓝渐变 │ │ │ │ │ │ 每个像素执行: │ │ │ │ │ │ color = mix(red, blue, interpolatedValue) │ │ │ │ │ │ return color │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ 深度/模板测试 (Depth/Stencil Test) │ │ │ │ │ │ ├─► 比较片元深度值与深度缓冲 │ │ │ │ │ │ ├─► 如果通过: 写入颜色和深度 │ │ │ │ │ │ └─► 如果失败: 丢弃片元 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ 颜色混合 (Color Blending) │ │ │ │ │ │ ├─► 将片元颜色与帧缓冲中现有颜色混合 │ │ │ │ │ │ ├─► 支持: 加法、乘法、Alpha混合等 │ │ │ │ │ │ └─► 输出: 最终颜色到帧缓冲 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ 注意: 每个像素都是并行处理的! │ │ │ │ 现代GPU有数千个核心,同时处理不同像素 │ │ │ └─────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ ⑨ vkCmdEndRenderPass() - GPU执行 │ │ │ │ ├─► 完成所有渲染操作 │ │ │ │ ├─► 执行布局转换 (COLOR_ATTACHMENT_OPTIMAL → PRESENT_SRC_KHR) │ │ │ │ └─► 准备图像用于呈现 │ │ │ └─────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ ⑩ 触发同步对象 │ │ │ │ ├─► GPU触发 renderFinishedSemaphore (渲染完成信号量) │ │ │ │ ├─► GPU触发 inFlightFences (栅栏) │ │ │ │ └─► 通知CPU渲染已完成 │ │ │ └─────────────────────────────────────────────────────────────────────────┘ │ │ │ │ 阶段3: 呈现 (Present) │ │ ════════════════════════════════════════════════════════════════════════════ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ ⑪ vkQueuePresentKHR() - CPU提交呈现请求 │ │ │ │ ├─► CPU构建呈现信息 │ │ │ │ ├─► 设置等待: renderFinishedSemaphore (渲染必须完成) │ │ │ │ ├─► 指定交换链和图像索引 │ │ │ │ └─► CPU→GPU: 提交呈现请求到呈现队列 │ │ │ └─────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ ⑫ GPU呈现引擎执行 │ │ │ │ ├─► GPU等待 renderFinishedSemaphore 触发 │ │ │ │ ├─► GPU将图像交给显示控制器 │ │ │ │ └─► GPU更新交换链状态 (标记图像为"正在显示") │ │ │ └─────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ ⑬ 显示控制器 (Display Controller) - 独立硬件 │ │ │ │ ├─► 在下一个VBlank垂直同步信号时读取图像 │ │ │ │ ├─► 将像素数据转换为显示信号 │ │ │ │ └─► 通过HDMI/DP发送到显示器 │ │ │ └─────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ ⑭ 显示器显示 │ │ │ │ ├─► 接收显示信号 │ │ │ │ ├─► 驱动LCD/OLED面板 │ │ │ │ └─► 用户看到三角形 │ │ │ └─────────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────┐ │ GPU内部流水线 (每个阶段并行) │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ 命令缓冲区 (Command Buffer) │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ [BeginRenderPass] [BindPipeline] [SetViewport] [Draw] [EndRenderPass]│ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 前端 (Frontend) │ │ │ │ ├─► 命令解析器: 解析命令缓冲区中的命令 │ │ │ │ ├─► 状态跟踪: 跟踪当前管线状态 │ │ │ │ └─► 任务分发: 将工作分发到各个流水线阶段 │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 顶点处理阶段 (Vertex Processing) │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ 顶点获取 (Vertex Fetch) │ │ │ │ │ │ ├─► 从顶点缓冲区读取顶点数据 │ │ │ │ │ │ └─► 每个顶点: 位置、颜色、法线、纹理坐标等 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ 顶点着色器 (Vertex Shader) - 并行处理所有顶点 │ │ │ │ │ │ ├─► 顶点0: 位置变换、计算颜色 │ │ │ │ │ │ ├─► 顶点1: 位置变换、计算颜色 │ │ │ │ │ │ ├─► 顶点2: 位置变换、计算颜色 │ │ │ │ │ │ └─► ... (如果有更多顶点) │ │ │ │ │ │ │ │ │ │ │ │ 每个顶点独立处理! 可以并行 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ 细分/几何处理 (可选) │ │ │ │ │ │ ├─► 细分着色器: 增加三角形数量 │ │ │ │ │ │ └─► 几何着色器: 生成/修改图元 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 光栅化阶段 (Rasterization) - 固定功能硬件 │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ 图元装配 (Primitive Assembly) │ │ │ │ │ │ ├─► 组装顶点为三角形 │ │ │ │ │ │ └─► 三角形: (v0, v1, v2) │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ 扫描转换 (Scan Conversion) │ │ │ │ │ │ ├─► 确定三角形覆盖了哪些像素 │ │ │ │ │ │ ├─► 对每个像素计算重心坐标 │ │ │ │ │ │ └─► 生成片元 (Fragment) │ │ │ │ │ │ │ │ │ │ │ │ 例子: 1920x1080分辨率 │ │ │ │ │ │ 三角形覆盖了约5000个像素 │ │ │ │ │ │ 生成5000个片元 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ 属性插值 (Attribute Interpolation) │ │ │ │ │ │ ├─► 对每个片元插值顶点属性 │ │ │ │ │ │ ├─► 颜色: 从红到蓝渐变 │ │ │ │ │ │ └─► 其他属性: 纹理坐标、法线等 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 片段处理阶段 (Fragment Processing) │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ 片段着色器 (Fragment Shader) - 并行处理所有片元 │ │ │ │ │ │ ├─► 片元0: 计算颜色 │ │ │ │ │ │ ├─► 片元1: 计算颜色 │ │ │ │ │ │ ├─► 片元2: 计算颜色 │ │ │ │ │ │ ├─► ... (5000个片元并行执行) │ │ │ │ │ │ └─► 输出: 每个片元的颜色值 │ │ │ │ │ │ │ │ │ │ │ │ 注意: 现代GPU有数千个核心! │ │ │ │ │ │ 所有片元同时执行 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ 深度/模板测试 (Per-Fragment Tests) │ │ │ │ │ │ ├─► 深度测试: 检查片元是否被遮挡 │ │ │ │ │ │ ├─► 模板测试: 检查模板值 │ │ │ │ │ │ └─► 通过: 继续; 失败: 丢弃片元 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ 颜色混合 (Color Blending) │ │ │ │ │ │ ├─► 读取帧缓冲中的现有颜色 │ │ │ │ │ │ ├─► 混合: newColor = src * srcFactor + dst * dstFactor │ │ │ │ │ │ └─► 写入帧缓冲 │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 写回 (Write Back) │ │ │ │ ├─► 将渲染结果写入交换链图像 │ │ │ │ ├─► 更新深度/模板缓冲 │ │ │ │ └─► 完成一个帧的渲染 │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────┐ │ CPU-GPU 同步机制 │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ 时间线 (多帧重叠执行): │ │ │ │ 帧 N-1 帧 N 帧 N+1 │ │ ├────────────────┼────────────────┼────────────────┤ │ │ │ │ CPU: │ │ ├──────────────────────────────────────────────────────────────────────────┤ │ │ │ Frame N-1: Frame N: Frame N+1: │ │ │ │ 录制命令 等待栅栏 录制命令 │ │ │ │ 提交命令 获取图像 提交命令 │ │ │ │ 呈现 录制命令 呈现 │ │ │ │ 提交命令 │ │ │ │ 呈现 │ │ │ └──────────────────────────────────────────────────────────────────────────┘ │ │ │ │ GPU: │ │ ├──────────────────────────────────────────────────────────────────────────┤ │ │ │ Frame N-1: Frame N: Frame N+1: │ │ │ │ 执行命令 等待图像可用 执行命令 │ │ │ │ 呈现 执行命令 呈现 │ │ │ │ 呈现 │ │ │ └──────────────────────────────────────────────────────────────────────────┘ │ │ │ │ 同步点: │ │ ● = 栅栏 (Fence) - CPU等待GPU │ │ ▲ = 信号量 (Semaphore) - GPU等待GPU │ │ ─ = 数据依赖 │ │ │ │ 帧 N 的详细同步: │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ CPU GPU │ │ │ │ │ │ │ │ │ │ │ ① vkWaitForFences() │ │ │ │ │ │ ● 等待上一帧完成 │ │ │ │ │ │ │ │ │ │ │ │ ② vkAcquireImage() │ │ │ │ │ │ ├──────────────────────►│ │ │ │ │ │ │ 请求图像 │ │ │ │ │ │ │ │ 等待显示控制器 │ │ │ │ │ │ │ 选择可用图像 │ │ │ │ │ │◄──────────────────────┤ │ │ │ │ │ │ 返回图像索引 │ │ │ │ │ │ │ │ ▲ 触发imageAvailableSemaphore │ │ │ │ │ │ │ │ │ │ │ ③ vkResetFences() │ │ │ │ │ │ ● 重置栅栏 │ │ │ │ │ │ │ │ │ │ │ │ ④ recordCommandBuffer() │ │ │ │ │ │ 录制命令 │ │ │ │ │ │ │ │ │ │ │ │ ⑤ vkQueueSubmit() │ │ │ │ │ │ ├──────────────────────►│ │ │ │ │ │ │ 提交命令 │ 等待imageAvailable │ │ │ │ │ │ │ 执行渲染 │ │ │ │ │ │ │ ▲ 触发renderFinished │ │ │ │ │ │ │ ● 触发Fence │ │ │ │ │ │ │ │ │ │ │ ⑥ vkQueuePresent() │ │ │ │ │ │ ├──────────────────────►│ │ │ │ │ │ │ 请求呈现 │ 等待renderFinished │ │ │ │ │ │ │ 交给显示控制器 │ │ │ │ │ │◄──────────────────────┤ │ │ │ │ │ │ 呈现完成 │ │ │ │ │ │ │ │ │ │ │ │ ⑦ currentFrame++ │ │ │ │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────┐ │ 性能优化关键点 │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ 1. 减少CPU-GPU同步等待 │ │ ├─► 使用双重/三重缓冲 (MAX_FRAMES_IN_FLIGHT = 2 或 3) │ │ ├─► CPU和GPU同时工作,互不阻塞 │ │ └─► 避免 vkDeviceWaitIdle() (除非必要) │ │ │ │ 2. 减少状态切换 │ │ ├─► 在命令缓冲区中批量录制命令 │ │ ├─► 对相似的绘制调用排序,减少管线切换 │ │ └─► 使用 push constants 代替 descriptor sets (更快的更新) │ │ │ │ 3. 减少数据传输 │ │ ├─► 尽可能在GPU显存中保持数据 (不频繁更新) │ │ ├─► 使用 staging buffer 异步传输顶点/纹理数据 │ │ └─► 避免每帧更新 large uniform buffers │ │ │ │ 4. 利用GPU并行性 │ │ ├─► 尽量使用 compute shader 做通用计算 │ │ ├─► 利用多线程录制命令缓冲区 (vkBeginCommandBuffer 支持) │ │ └─► 使用 indirect draws 减少CPU开销 │ │ │ │ 5. 呈现模式选择 │ │ ├─► MAILBOX: 低延迟,无撕裂 (推荐游戏) │ │ ├─► FIFO: 垂直同步,稳定 (推荐视频播放) │ │ └─► IMMEDIATE: 最低延迟,可能撕裂 (推荐VR) │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘