从 Instance 到 Swapchain,从同步原语到引擎 RHI 封装——
一份打通底层原理与顶层应用的图形知识地图。
手动调用 vkAllocateMemory,精确控制每个资源的堆类型、对齐与绑定。驱动不再替你"猜测"最佳策略。
所有渲染状态(深度/混合/光栅化)在 VkPipeline 创建时一次性固定,消除绘制时的驱动重编译开销。
Fence / Semaphore / Barrier 三层同步体系,开发者完全掌控 CPU↔GPU、队列间、资源状态转换的时序。
Command Buffer 与线程解耦,多个工作线程可并行录制,最后统一提交——这是现代引擎并行渲染的基础。
Vulkan 的所有对象构成严格的依赖有向图,理解层级是一切的前提。
VkPhysicalDeviceProperties(设备名/类型/限制)和 VkPhysicalDeviceFeatures(几何着色器、各向异性过滤等)。直接调用 vkAllocateMemory 有严格上限(maxMemoryAllocationCount,通常 4096)。VMA 是 AMD 开源的内存管理库,内部实现了大块分配 + 子分配,自动选择最优堆类型。
| 队列类型 | 用途 | 引擎对应 | 同步方式 |
|---|---|---|---|
| Graphics | DrawCall、RenderPass 执行 | UE RHI Thread / Unity Render Thread | Semaphore + Fence |
| Compute | Compute Shader、GPGPU 计算 | UE AsyncComputeQueue / Unity Compute | Semaphore(跨队列) |
| Transfer | Buffer/Image 上传下载 | UE StreamingManager / Asset Loading | Semaphore(完成后通知渲染) |
与特定 Queue Family 绑定,线程独占(不可跨线程共享 Pool)
VkCommandPoolCreateInfo poolInfo{};
poolInfo.queueFamilyIndex = graphicsFamily;
Primary(可直接提交)或 Secondary(嵌入到 Primary 中执行)
allocInfo.commandBufferCount = 1;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
vkBeginCommandBuffer → 各类 vkCmd* → vkEndCommandBuffer
vkBeginCommandBuffer(cmdBuf, &beginInfo);
vkCmdBeginRenderPass(...);
vkCmdBindPipeline(...);
vkCmdDraw(...);
vkCmdEndRenderPass(...);
vkEndCommandBuffer(cmdBuf);
vkQueueSubmit 附带等待/信号 Semaphore 及完成 Fence
VkSubmitInfo submitInfo{};
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &imgAvailSem;
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &renderDoneSem;
vkQueueSubmit(graphicsQueue, 1, &submitInfo, fence);
Vulkan 的管线是不可变的(Immutable PSO)——这是其性能优势的核心来源。
vkCreateGraphicsPipelines 时一次性烘焙完成。驱动可在此时完成着色器特化(Specialization)和寄存器分配,后续 DrawCall 零开销切换。VkGraphicsPipelineCreateInfo pipelineInfo{}; // ① Shader Stages — SPIR-V 二进制着色器模块 pipelineInfo.stageCount = shaderStages.size(); pipelineInfo.pStages = shaderStages.data(); // → Unity: ComputeShader asset / HDRP ShaderGraph 编译后的 SPIR-V // → UE: FVulkanShader, 从 HLSL via DXC 编译得到 // ② Vertex Input — 顶点属性描述(stride / format / location) pipelineInfo.pVertexInputState = &vertexInput; // → UE: FVertexDeclaration, 对应 RHI 的 FVertexElement // ③ Input Assembly — 拓扑类型 pipelineInfo.pInputAssemblyState = &inputAssembly; // TRIANGLE_LIST 等 // ④ Viewport & Scissor pipelineInfo.pViewportState = &viewportState; // ⑤ Rasterization — 正面方向、剔除模式、填充模式、深度偏移 pipelineInfo.pRasterizationState = &rasterizer; // → Unity: RasterState in ShaderLab Pass 对应此处 // ⑥ Multisample — MSAA 采样数 pipelineInfo.pMultisampleState = &multisampling; // ⑦ Depth & Stencil Test pipelineInfo.pDepthStencilState = &depthStencil; // → Unity: ZWrite / ZTest / Stencil { ... } 对应此处 // ⑧ Color Blend — 每个附件的混合方程 pipelineInfo.pColorBlendState = &colorBlending; // → Unity: Blend SrcAlpha OneMinusSrcAlpha 对应此处 // ⑨ Pipeline Layout — Push Constants + Descriptor Set 布局 pipelineInfo.layout = pipelineLayout; // ⑩ Render Pass — 指定此管线在哪个 Subpass 中使用 pipelineInfo.renderPass = renderPass; pipelineInfo.subpass = 0;
定义"模板":binding 0 是 UBO,binding 1 是 Sampler2D...
// 类比 C++ struct 的定义
layout(set=0, binding=0) uniform CameraUBO { ... };
layout(set=0, binding=1) uniform sampler2D albedo;
预先分配固定数量的描述符槽位
poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
poolSizes[0].descriptorCount = MAX_FRAMES;
实际持有资源引用的"实例"
// 写入实际资源
VkWriteDescriptorSet write{};
write.dstBinding = 0;
write.pBufferInfo = &bufferInfo;
vkUpdateDescriptorSets(device, 1, &write, 0, nullptr);
录制时绑定到管线
vkCmdBindDescriptorSets(
cmdBuf,
VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayout, 0, 1,
&descriptorSet, 0, nullptr);
| Set 编号 | 更新频率 | 典型内容 | 引擎对应 |
|---|---|---|---|
| Set 0 | 每帧一次 | View/Projection 矩阵、时间、摄像机参数 | UE: SceneUniformBuffer / Unity: PerFrameBuffer |
| Set 1 | 每 Pass 一次 | 灯光数据、阴影贴图 | UE: ViewUniformBuffer |
| Set 2 | 每材质一次 | Albedo/Normal/Roughness 贴图、材质参数 | UE: MaterialUniformBuffer |
| Set 3 | 每物体一次 | Model 矩阵、骨骼矩阵 | UE: PrimitiveUniformBuffer |
| 操作 | 含义 | 性能影响 | 使用场景 |
|---|---|---|---|
| LOAD_OP_CLEAR | 渲染前清除附件 | ✅ 移动端友好(避免加载) | 每帧重绘的颜色/深度 |
| LOAD_OP_LOAD | 保留上一帧内容 | ⚠ 移动端需从内存加载 | TAA 需要历史帧时 |
| LOAD_OP_DONT_CARE | 内容未定义,不关心 | ✅ 最优(驱动可跳过) | 深度附件(通常不需要保留) |
| STORE_OP_STORE | 写回内存 | ⚠ 移动端需写出 Tile | 需要后续采样的附件 |
| STORE_OP_DONT_CARE | 丢弃内容 | ✅ 移动端可留在 Tile | 中间深度缓冲(不需要读回) |
Vulkan 假设所有操作默认是无序的,开发者必须显式声明依赖关系。
CPU 等待 GPU 完成某批工作。典型用途:等待上一帧渲染结束后,才重用 Command Buffer / 读取结果。
// 提交时附带 Fence
vkQueueSubmit(queue, 1, &submitInfo, inFlightFence);
// CPU 阻塞等待
vkWaitForFences(device, 1, &inFlightFence,
VK_TRUE, UINT64_MAX);
vkResetFences(device, 1, &inFlightFence);
一个队列提交完成后,通知另一个队列可以开始。典型用途:Swapchain 图像可用后,通知渲染队列开始渲染。
// 渲染等待 imageAvailable Semaphore
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &imageAvailableSem;
submitInfo.pWaitDstStageMask = &waitStage;
// 渲染完成后发出 renderFinished 信号
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &renderFinishedSem;
vkAcquireNextImageKHR 获取可用的后台缓冲索引,vkQueuePresentKHR 将渲染完成的图像提交显示。
vkCmdPipelineBarrier 显式声明,指定 srcStage/dstStage 和 srcAccess/dstAccess 掩码。理解 Vulkan 如何被 Unity / UE 抽象为高层 RHI,以及现代引擎的前沿设计。
NativeRenderPassAttachment,将 SRP 的 Pass 直接映射到 VkRenderPass 的 SubpassCommandBuffer.SetRenderTarget → 内部翻译为 vkCmdBeginRenderPass + Attachment 配置Engine/Source/Runtime/VulkanRHI/
├── Private/
│ ├── VulkanDevice.cpp ← 逻辑设备管理
│ ├── VulkanMemory.cpp ← 内存分配(类VMA)
│ ├── VulkanPipeline.cpp ← PSO 创建与缓存
│ ├── VulkanDescriptorSets.cpp ← Descriptor 管理
│ ├── VulkanCommandBuffer.cpp ← 命令录制
│ └── VulkanQueue.cpp ← 队列提交
└── Public/
├── VulkanRHI.h
└── VulkanResources.h
VK_EXT_descriptor_indexinglayout(set=0, binding=0) uniform sampler2D textures[];VkCommandPool(Command Pool 不是线程安全的)。使用 Secondary Command Buffer 让工作线程录制后,由主线程通过 vkCmdExecuteCommands 合并。
Vulkan 的 Pipeline 创建(vkCreateGraphicsPipelines)是耗时操作。首次遇到新材质/新 Pass 组合时,驱动需要编译 SPIR-V → ISA,可能造成数十毫秒卡顿。
OpenGL vs Vulkan vs DirectX 12 vs Metal — 核心设计哲学差异。
| 维度 | OpenGL | Vulkan | DirectX 12 | Metal |
|---|---|---|---|---|
| 控制粒度 | 隐式,驱动决定 | 完全显式,开发者控制 | 完全显式 | 半显式,部分托管 |
| 状态模型 | 全局状态机(Context) | 对象模型,无全局状态 | 对象模型 | 对象模型 |
| 错误处理 | 运行时(glGetError) | 验证层(可选)+ 调试回调 | 调试层 + 错误码 | 调试工具集成 |
| 跨平台 | 跨平台(OpenGL ES 移动端) | 跨平台(PC/Android/Switch) | 仅 Windows/Xbox | 仅 Apple 生态 |
| 学习曲线 | 较低,快速上手 | 极高,初始化代码量庞大 | 极高(与 Vulkan 相当) | 中等,文档友好 |
| 维度 | OpenGL | Vulkan | DirectX 12 | Metal |
|---|---|---|---|---|
| 内存分配 | 驱动自动管理 | vkAllocateMemory + VMA,开发者全权负责 | CreateCommittedResource / CreatePlacedResource | newBufferWithLength: 自动堆管理 |
| 内存类型 | STREAM/DYNAMIC/STATIC hint | 显式堆属性(DEVICE_LOCAL / HOST_VISIBLE 等) | D3D12_HEAP_TYPE(DEFAULT/UPLOAD/READBACK) | MTLStorageMode(Private/Shared/Managed) |
| 纹理上传 | glTexImage2D(驱动内部 Staging) | 手动 Staging Buffer → vkCmdCopyBufferToImage | UpdateSubresources(内部 Staging) | blitCommandEncoder copyFromBuffer |
| 内存泄漏 | 驱动处理,GC 风格 | 开发者必须显式 vkFreeMemory,否则泄漏 | COM 引用计数 | ARC 自动管理 |
| 维度 | OpenGL | Vulkan | DirectX 12 | Metal |
|---|---|---|---|---|
| 管线状态 | 运行时可随时修改全局状态 | PSO 预编译,不可变对象 | PSO(D3D12_GRAPHICS_PIPELINE_STATE_DESC) | MTLRenderPipelineState(预编译) |
| 状态切换开销 | 驱动在 DrawCall 时重新校验状态,开销隐式 | 绑定不同 Pipeline 对象,极低开销 | SetPipelineState 开销低 | setRenderPipelineState 开销低 |
| 着色器格式 | GLSL 源码(运行时编译) | SPIR-V 二进制(离线编译) | HLSL → DXBC/DXIL | MSL / AIR 中间表示 |
| 着色器变体 | 运行时 #define 宏展开 | Specialization Constants(编译期优化) | 类似 SPIR-V | 函数常量(Function Constants) |
| 维度 | OpenGL | Vulkan | DirectX 12 | Metal |
|---|---|---|---|---|
| CPU-GPU 同步 | glFinish/glFlush(隐式等待) | Fence(显式,精确等待) | Fence + Signal/Wait Value | addCompletedHandler / waitUntilCompleted |
| GPU-GPU 同步 | glTextureBarrier / 驱动自动推断 | Semaphore(队列间)+ Pipeline Barrier(资源状态) | ResourceBarrier + ExecuteCommandLists 依赖 | MTLFence / MTLEvent |
| 资源状态 | 驱动自动追踪,开发者不感知 | 开发者显式管理 Image Layout + Access Mask | ResourceBarrier(D3D12_RESOURCE_STATES) | 部分自动(Hazard Tracking) |
| 隐患 | 驱动过保守,可能插入多余 Stall | 遗漏 Barrier 导致未定义行为,难以调试 | 同 Vulkan,但错误提示更友好 | Hazard Tracking 可能有隐式开销 |
| 维度 | OpenGL | Vulkan | DirectX 12 | Metal |
|---|---|---|---|---|
| 命令录制 | Context 不可多线程共享(除非 ARB_debug_output) | 每线程独立 CommandPool + CommandBuffer,天然线程安全 | CommandAllocator 每线程独立,CommandList 可并行录制 | 每线程独立 CommandBuffer 编码器 |
| 提交线程 | 只能从拥有 Context 的线程调用 | vkQueueSubmit 需外部加锁,但录制无锁 | ExecuteCommandLists 需序列化 | commit 需序列化 |
| 引擎实践 | 单渲染线程,Deferred Context(DX11) | UE Task Graph / Unity Jobs + 多 Worker 并行录制 | 同 Vulkan 策略 | 同 Vulkan 策略 |
| 维度 | OpenGL | Vulkan | DirectX 12 | Metal |
|---|---|---|---|---|
| 着色器语言 | GLSL | SPIR-V(中间语言,源语言不限) | HLSL → DXBC/DXIL | MSL(Metal Shading Language) |
| 编译时机 | 运行时由驱动编译 | 离线编译为 SPIR-V,驱动只做最后 ISA 编译 | 离线预编译为 DXBC/DXIL | 离线编译为 .metallib |
| 引擎工具链 | ShaderLab(Unity)→ GLSL | HLSL → DXC → SPIR-V(Unity HDRP/UE 均采用) | HLSL → DXC → DXIL | HLSL / MSL → xcrun metal |
| 反射 | glGetActiveUniform 运行时反射 | SPIRV-Reflect / glslang 离线反射生成绑定表 | D3D Reflection API | MTLLibrary.newFunctionWithName |
从 SGI 的 GL 到现代显式 API——四十年的进化史。
Iris GL 的开放版本,固定功能管线,立即渲染模式(glBegin/glEnd),状态机设计,革命性地统一了跨平台图形接口。
Windows 平台的直接硬件访问接口,Direct3D 的前身。与 OpenGL 形成二十年的竞争格局。
GLSL 引入,Vertex/Fragment Shader 取代固定管线。GPU 从专用图形硬件走向通用可编程计算平台。
移动端可编程着色(OpenGL ES 2.0 成为智能手机 GPU 标准);DX11 引入 Compute Shader,标志 GPGPU 时代开始。
AMD 发布 Mantle,第一个向开发者暴露接近硬件的低级 API。验证了显式控制的性能潜力,直接催生了 Vulkan 和 DX12。
Apple 率先发布 Metal,低开销 API 登陆 iOS。Microsoft 同年发布 DX12 预告——显式 API 时代正式开幕。
Khronos 基于 Mantle 发布 Vulkan 1.0。跨平台、显式、低开销、多线程友好——成为 OpenGL 的现代继承者。同年 UE 4.12、Unity 5.4 开始支持。
子组操作(Subgroup)、多视图渲染(Multiview);NVIDIA 发布 RTX,DX12 和 Vulkan 扩展开始纳入光线追踪支持。
Timeline Semaphore(精细化同步)、Buffer Device Address(Bindless 基础)、光线追踪扩展正式转正。UE5 开始 Lumen 全局光照研发,深度依赖 Vulkan 的现代特性。
Dynamic Rendering(简化 RenderPass 创建)、Mesh/Task Shader 正式标准化。UE5.0 发布,Nanite 和 Lumen 完全基于现代显式 API 特性构建。
Vulkan Safety Critical(航空/汽车行业);WebGPU 成为浏览器中的 Vulkan-like API,将显式 GPU 编程带入 Web 生态。DirectX 12 Agility SDK 持续迭代。