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失败,查了两天才发现。

注意:GC7000的寄存器基地址和中断号,不同型号的i.MX8(如8M、8M Mini、8M Nano)都不一样。别拿参考设计直接抄,一定要看对应芯片的Reference Manual。

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

核心要点:Vulkan的调试比OpenGL ES难得多。我建议先用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部分。
警告:千万不要在生产环境里直接打开galcore的debugfs节点,它会降低性能。我一般只在调试时临时挂载debugfs。

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工具可以抓取每一帧的渲染指令、纹理上传、顶点处理等数据。用法是:

  1. 在板子上运行gpu_profilerd守护进程。
  2. 在PC端运行GPU_Profiler客户端,通过网络连接板子。
  3. 选择要分析的进程,开始抓帧。

这个工具能直接看到每个Draw Call的耗时。我遇到过最夸张的情况,一个Draw Call因为纹理格式不对,触发了GPU的软件回退,耗时从0.1ms变成了15ms。

提示:如果手头没有GPU Profiler,可以用adb shell dumpsys gfxinfo看每一帧的绘制时间。虽然数据没Profiler详细,但胜在方便,不用装额外工具。

12.5 知识体系总览

下面这张图,是我自己总结的GC7000图形栈全貌。从内核到应用层,每个环节都标出来了。你移植的时候,可以对照着这张图,看看哪一层出了问题。

GC7000 图形软件栈 应用层 (App / Game / Launcher) Android框架 (SurfaceFlinger / HWUI / Vulkan API) 用户空间驱动 (libGAL / libGLESv2 / libvulkan_vivante) 内核驱动 (etnaviv / galcore + DRM + ION) 硬件层 (GC7000 GPU + 显示控制器 + 内存)

嗯,图形这块内容确实不少。但说白了,只要把驱动移植、API支持、内存管理、性能分析这四个环节打通,GC7000在Android Automotive上就能跑得稳稳当当。我这些年踩过的坑,基本都集中在这四个环节里。你按这个顺序来,能少走不少弯路。


公众号:蓝海资料掘金营,微信deep3321