2. 嵌入式图形系统概述

嵌入式图形系统与桌面PC图形栈的最大区别,在于它必须在功耗、带宽、面积和实时性四重约束下完成渲染与显示。理解这一约束链条,是后续学习Vulkan在嵌入式平台落地的起点。本节从GPU架构、显示子系统、内存模型和API选型四个维度建立整体认知,并说明Vulkan为何成为现代嵌入式图形的主流选择。

一、嵌入式GPU架构:从统一渲染到Tile-Based

嵌入式GPU普遍采用统一着色器架构(Unified Shader),顶点、像素、计算共用同一组ALU,避免固定管线时代的硬件冗余。更关键的是,移动与嵌入式GPU几乎都采用Tile-Based Rendering(TBR)或Tile-Based Deferred Rendering(TBDR):把帧缓冲切分为小块(通常16×16或32×32像素),逐块在片上高速缓存(On-Chip Memory / Tile Memory)中完成光栅化与混合,最后一次性写回系统内存。

这种设计带来两个直接后果:其一,外部内存带宽大幅下降,因为颜色和深度缓冲的读写被限制在片上;其二,渲染必须按Tile顺序推进,任何需要跨Tile访问的操作(如依赖前一帧结果的反馈、随机纹理写入)都会触发昂贵的Tile flush。因此,在嵌入式GPU上,带宽比算力更稀缺,减少Render Pass切换、避免不必要的Load/Store是性能优化的核心。

二、显示子系统:从GPU输出到屏幕

GPU渲染完成后,图像需要经过显示控制器(Display Controller)、图层合成器(Compositor)和物理接口才能到达屏幕。嵌入式平台常见接口包括MIPI DSI、HDMI、eDP和LVDS。显示子系统通常支持多个硬件图层(Plane),可将UI层、视频层、光标层直接叠加,无需GPU参与合成,从而降低功耗。

与Vulkan直接相关的是呈现(Present)路径:应用通过交换链(Swapchain)提交图像,显示控制器在垂直同步(VSync)时切换缓冲区。若渲染耗时超过一帧预算,就会出现掉帧或撕裂。嵌入式系统常采用双缓冲或三缓冲,并通过Fence同步GPU与显示控制器,确保不读取正在被扫描输出的缓冲区。

嵌入式图形系统数据流与Vulkan定位 应用 / 引擎 Vulkan API 调用 Vulkan Runtime 验证层 / 驱动 ICD 嵌入式 GPU 统一着色器 + TBR Tile Memory 片上缓存 系统内存 / 显存 统一内存 UMA DMA-BUF 共享 显示子系统 Display Controller MIPI DSI / HDMI DMA-BUF API 选型维度 功耗 · 驱动成熟度 · 生态 实时性 · 可移植性 Vulkan 定位 显式控制 · 低CPU开销 多线程 · 跨平台 关键约束 带宽 · 功耗 · 面积 实时帧预算

三、内存模型:UMA、DMA-BUF与显式管理

嵌入式SoC通常采用统一内存架构(UMA),CPU与GPU共享同一物理内存,没有独立显存。这降低了成本,但也意味着GPU与CPU争抢带宽。Vulkan在嵌入式上的内存模型围绕设备本地内存(Device Local)与主机可见内存(Host Visible)展开,应用需显式选择内存类型并管理映射。

跨设备共享图像时,Linux平台普遍使用DMA-BUF:GPU渲染到DMA-BUF,显示控制器或视频解码器直接引用同一缓冲区,避免拷贝。Vulkan通过外部内存扩展(VK_KHR_external_memory)与DMA-BUF互操作。典型配置如下:

// 查询物理设备内存类型
VkPhysicalDeviceMemoryProperties memProps;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProps);

// 选择:设备本地 + 主机可见(UMA常见)
for (uint32_t i = 0; i < memProps.memoryTypeCount; i++) {
    VkMemoryPropertyFlags f = memProps.memoryTypes[i].propertyFlags;
    if ((f & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) &&
        (f & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
        // 适合动态顶点/Uniform缓冲
    }
}
常见误区:把桌面独显的“显存”概念直接套到嵌入式UMA平台,认为上传纹理后数据会“留在显存”。在UMA中,纹理与系统内存是同一块物理内存,真正影响性能的是访问模式与缓存一致性,而非“上传”动作本身。

四、图形API选型与Vulkan的定位

嵌入式可选API包括OpenGL ES、Vulkan、Metal(Apple平台)以及厂商私有API。选型需综合驱动成熟度、功耗、实时性和生态。下表给出对比:

维度 OpenGL ES Vulkan 私有API
CPU开销 较高,驱动隐式管理 低,显式命令缓冲 低但不可移植
多线程 受限 原生支持 视实现而定
功耗控制 一般 精细,可减少冗余 最优但绑定厂商
可移植性 高 高(Android/Linux) 低
学习曲线 平缓 陡峭 陡峭

Vulkan的定位可以概括为:把驱动隐式做的事交还给应用。它提供显式的内存分配、命令缓冲录制、管线状态和同步原语,使应用能针对TBR架构精确控制Render Pass的Load/Store操作,减少Tile flush,从而在嵌入式平台上获得更稳定的帧率和更低的功耗。同时,Vulkan的跨平台特性使其成为Android、Linux嵌入式设备上长期可维护的图形方案。

选型建议:若项目对实时性和功耗敏感、团队具备图形底层能力,优先Vulkan;若为快速原型或旧设备兼容,OpenGL ES仍是合理起点。两者并非互斥,许多引擎同时提供后端。

五、案例与复盘:一个典型的嵌入式渲染帧

以车载仪表盘为例:UI层由GPU渲染到DMA-BUF,视频层由硬件解码器输出,显示控制器将两层叠加。若使用OpenGL ES,驱动可能在每帧结束时隐式同步,导致CPU等待;改用Vulkan后,应用通过信号量(Semaphore)和栅栏(Fence)显式编排:

// 提交渲染命令,等待图像可用信号量
VkSubmitInfo submit = {};
submit.waitSemaphoreCount = 1;
submit.pWaitSemaphores = &imageAvailable;
submit.pWaitDstStageMask = &waitStage;
submit.commandBufferCount = 1;
submit.pCommandBuffers = &cmd;
submit.signalSemaphoreCount = 1;
submit.pSignalSemaphores = &renderFinished;
vkQueueSubmit(graphicsQueue, 1, &submit, inFlightFence);