12、图形与GPU加速:GPU驱动(GC7000系列)移植、OpenGL ES与Vulkan支持、GPU内存管理、图形性能分析工具
好,咱们今天聊点硬核的——GPU。说实话,在Android系统里,GPU这块要是没整明白,那UI卡顿、游戏掉帧、甚至黑屏重启,都是家常便饭。我这些年做i.MX平台移植,GC7000系列GPU打交道最多。嗯,咱们就从驱动移植开始,一步步把图形栈给它捋顺了。
12.1 GC7000系列GPU驱动移植
GC7000是Vivante家的IP核,在NXP的i.MX8系列里用得特别多。移植驱动,说白了就是让内核能认这块GPU,并且把用户空间的OpenGL ES/Vulkan库给对接上。
12.1.1 内核驱动层
内核驱动主要分两部分:一个是DRM(Direct Rendering Manager)框架下的etnaviv驱动,另一个是Vivante原厂的galcore驱动。我个人习惯用etnaviv,因为它是主线内核的一部分,维护性好。
移植的第一步,是确认设备树里GPU节点配对了。我遇到过好几次,板子起来后GPU死活不工作,最后发现是时钟频率写错了。
// 设备树中GC7000的典型配置
gpu: gpu@0x38000000 {
compatible = "vivante,gc7000";
reg = <0x0 0x38000000 0x0 0x40000>;
interrupts = <0 3 4>;
clocks = <&clk IMX8MQ_CLK_GPU>;
clock-names = "core";
power-domains = <&pgc_gpu>;
};
这里有个坑——interrupts的配置。GC7000支持多个中断线,但有些板子的设计只连了一个。我曾经因为多配了一个不存在的IRQ,导致驱动probe失败,查了两天才发现。
12.1.2 用户空间库
内核驱动起来后,用户空间还需要libGAL、libGLESv2、libVulkan这些库。这些库通常由芯片厂商提供闭源二进制,或者从Yocto的meta-freescale层里拿。
我建议的做法是:
- 先确认内核版本和用户空间库的匹配关系。比如Linux 5.4配的是Vivante 6.4.0.p2版本的库。
- 用
strings libGLESv2.so | grep GC查看库支持的GPU型号。如果输出里没有"GC7000",那肯定不对。 - 把库放到
/vendor/lib/egl/和/vendor/lib/hw/下,并确保selinux上下文正确。
dmesg | grep galcore能看到"Galcore version X.X.X"但glmark2跑不起来,多半是用户空间库和内核驱动版本不匹配。我遇到过最离谱的一次,是库比内核新了两个大版本,结果所有EGL调用都返回空指针。
12.2 OpenGL ES与Vulkan支持
GC7000原生支持OpenGL ES 3.2和Vulkan 1.1。但要注意,Vulkan的支持不是默认就有的,需要额外配置。
12.2.1 OpenGL ES的配置
Android的图形系统依赖EGL和OpenGL ES。在BoardConfig.mk里,需要指定GPU厂商:
# BoardConfig.mk
TARGET_BOARD_PLATFORM_GPU := vivante
BOARD_GPU_DRIVERS := etnaviv
然后,在device.mk里把对应的库打包进系统:
PRODUCT_PACKAGES += \
libGLESv2 \
libEGL \
libGLESv1_CM \
libgles2_impl \
libgles1_impl
嗯,这里有个细节。GC7000的EGL实现里,eglInitialize会去扫描/dev/dri/card0或者/dev/galcore。如果设备节点权限不对,EGL初始化就会失败。我建议在init.rc里加一句:
chmod 0666 /dev/galcore
12.2.2 Vulkan的支持
Vulkan的支持相对复杂一些。GC7000的Vulkan驱动是闭源的,叫libvulkan_vivante.so。你需要确保:
- 内核开启了Vulkan需要的IOMMU支持。
- 用户空间有
/vendor/lib/libvulkan_vivante.so和对应的JSON配置文件。 - 在
device.mk里添加PRODUCT_PACKAGES += libvulkan_vivante。
我踩过的一个坑:Vulkan驱动依赖一个叫libgc_vulkan_wsi.so的库,这个库负责窗口系统集成(Wayland或Android的ANativeWindow)。如果这个库没放对位置,vkCreateSwapchainKHR会直接返回VK_ERROR_INITIALIZATION_FAILED。
vulkaninfo工具确认驱动是否加载成功,再用vkcube验证渲染管线是否正常。
12.3 GPU内存管理
GPU内存管理,说白了就是显存怎么分配、怎么回收、怎么和CPU共享。GC7000用的是统一内存架构(UMA),GPU和CPU共享物理内存。好处是省了显存颗粒,坏处是内存带宽容易成为瓶颈。
12.3.1 CMA与ION
Android系统里,GPU内存分配通常走ION或DMA-BUF。GC7000的驱动默认用ION,但我在移植时发现,如果内核开启了CMA(Contiguous Memory Allocator),性能会更好。
为什么呢?因为GPU需要连续物理内存来做帧缓冲和纹理。CMA能保证分配大块连续内存,减少GPU的TLB miss。
// 内核配置中开启CMA
CONFIG_CMA=y
CONFIG_DMA_CMA=y
CONFIG_CMA_SIZE_MBYTES=256
我建议CMA大小至少设256MB。如果板子内存只有1GB,那128MB也行,但跑大型游戏可能会卡。
12.3.2 内存泄漏排查
GPU内存泄漏是移植过程中最头疼的问题之一。我曾经遇到一个case:系统运行两天后,所有OpenGL ES调用都返回EGL_BAD_ALLOC。最后发现是某个服务在创建纹理后没有调用glDeleteTextures。
排查方法:
- 用
cat /sys/kernel/debug/galcore/mem查看当前GPU内存使用情况。 - 用
dumpsys gfxinfo查看每个进程的GPU内存占用。 - 如果怀疑是某个App泄漏,可以用
adb shell dumpsys meminfo <pid>看Graphics部分。
12.4 图形性能分析工具
工具用得好,下班下得早。我整理了一套GC7000上常用的性能分析工具链,分享给大家。
12.4.1 常用工具一览
| 工具名 | 用途 | 安装方式 |
|---|---|---|
| glmark2 | OpenGL ES基准测试 | Yocto: glmark2 recipe |
| vkcube | Vulkan基本功能验证 | Android: external/vkcube |
| GPU Profiler | Vivante官方性能分析 | 需向NXP申请 |
| perf | 内核级GPU事件采样 | 内核自带 |
| simpleperf | Android用户空间采样 | Android SDK自带 |
12.4.2 实战:用glmark2定位瓶颈
跑glmark2时,我习惯加上--fullscreen和--off-screen参数,避免窗口管理器干扰。如果分数偏低,我会看具体哪个场景卡顿。
# 跑全场景测试
glmark2-es2 --fullscreen --off-screen
# 只跑纹理相关场景
glmark2-es2 --fullscreen --off-screen --benchmark :texture
我记得有一次,纹理场景分数只有正常值的1/3。查了半天,发现是GPU的L2缓存被其他IP核占用了。最后通过调整内存分区,把GPU的缓存区域隔离出来,分数就上去了。
12.4.3 用GPU Profiler抓帧
Vivante的GPU Profiler工具可以抓取每一帧的渲染指令、纹理上传、顶点处理等数据。用法是:
- 在板子上运行
gpu_profilerd守护进程。 - 在PC端运行
GPU_Profiler客户端,通过网络连接板子。 - 选择要分析的进程,开始抓帧。
这个工具能直接看到每个Draw Call的耗时。我遇到过最夸张的情况,一个Draw Call因为纹理格式不对,触发了GPU的软件回退,耗时从0.1ms变成了15ms。
adb shell dumpsys gfxinfo看每一帧的绘制时间。虽然数据没Profiler详细,但胜在方便,不用装额外工具。
12.5 知识体系总览
下面这张图,是我自己总结的GC7000图形栈全貌。从内核到应用层,每个环节都标出来了。你移植的时候,可以对照着这张图,看看哪一层出了问题。
嗯,图形这块内容确实不少。但说白了,只要把驱动移植、API支持、内存管理、性能分析这四个环节打通,GC7000在Android Automotive上就能跑得稳稳当当。我这些年踩过的坑,基本都集中在这四个环节里。你按这个顺序来,能少走不少弯路。