
先别急着写kernel把这十几行代码跑通再说。我手头这块RK3288的板子拿到的出厂SDK里连OpenCL的“Hello World”都没配好网上搜到的资料要么是桌面显卡的教程要么是Android层通过Java调OpenCL的片段真正在C语言层面把Platform和Device信息一行行打出来的完整例子反而不多。OpenCL在嵌入式平台上的第一步从来都不是写计算逻辑而是先确认三件事驱动在不在、GPU能不能被枚举到、驱动到底支持到哪个OpenCL版本。就这一个“打印信息”的小需求里面能踩的坑一点都不少。这篇文章就把我在RK3288上打印OpenCL平台与设备信息的完整过程写出来代码可以直接抄坑也可以直接避开。1. 为什么在嵌入式平台第一步永远是打印信息1.1 嵌入式OpenCL环境比PC“娇气”得多先纠正一个误区OpenCL的“平台信息”在PC上确实有点鸡肋因为一个驱动管一个平台闭着眼也能猜出结果。但在RK3288这类ARM Linux设备上情况完全不同。同一颗SoC不同的固件、不同的GPU驱动版本、不同的libOpenCL.so实现枚举出来的平台数量、设备类型、支持的特性都可能不一样。我手头这块板子刷过一次官方新固件之后OpenCL版本就从1.0变到了1.1连CL_DEVICE_MAX_WORK_ITEM_SIZES这种基础参数都有变化。如果上来就照着网上的例程写kernel很可能因为驱动行为不一致而莫名失败。简单说嵌入式OpenCL环境不是“装好就能用”而是“确认过才能用”。所以写任何OpenCL程序之前把平台、设备信息打印出来就是在给自己带个保险。花五分钟把环境摸清楚比写完几百行代码再回来查问题省下来的时间是以小时计的。1.2 Platform和Device到底在描述什么这两个概念很多初学者会混淆。Platform对应的是“OpenCL实现”通常由GPU厂商或SoC厂商提供的运行时库暴露出来Device才是真正干活的硬件比如RK3288里的Mali-T764 GPU。类比一下Platform是“仓库管理系统”Device是“仓库里的一台叉车”。同一个管理系统可以管理多台叉车但不一定是所有叉车都适合干活——有些可能是CPU模拟的有些才是真GPU。在RK3288上通常只枚举出一个平台ARM Platform设备则有可能列出Mali-T764 GPU甚至在某些带GPU驱动但显示子系统没初始化的情况下还会枚举出CPU设备具体取决于驱动怎么实现。这里有一个很隐蔽的规则CL_DEVICE_TYPE_ALL并不总是你想要的。有些驱动在枚举GPU时如果显示节点没起来GPU可能不会暴露这时候用CL_DEVICE_TYPE_ALL反而可能只拿到一个CPU设备。真正做计算的时候我习惯先枚举所有设备再用CL_DEVICE_TYPE_GPU单独查一次对比两次结果避免选错设备。1.3 打印信息能帮你避开的三个大坑第一个坑是驱动是否被正确加载。RK3288常见的情况是GPU驱动编译进了内核但用户态的libOpenCL.so没安装或者装的是个空壳实现。此时clGetPlatformIDs要么返回0个平台要么直接返回CL_PLATFORM_NOT_FOUND_KHR这类扩展错误码。看到0个平台就别急着写kernel先回去查库文件是否完整。第二个坑是OpenCL版本。Mali-T764这颗GPU早期驱动只报OpenCL 1.1如果程序里直接用了clCreateCommandQueueWithProperties这种OpenCL 2.0才有的接口链接没问题运行时必然报CL_INVALID_VALUE。先打印CL_PLATFORM_VERSION和CL_DEVICE_VERSION等于提前给代码划了一条能力边界后面写kernel时哪些特性能用、哪些不能用一目了然。第三个坑是内存参数。设备侧CL_DEVICE_GLOBAL_MEM_SIZE、CL_DEVICE_LOCAL_MEM_SIZE直接决定你的数据分块策略和work group大小上限。如果不知道GPU侧一块local memory只有几十KB你写的kernel可能连编译都过不去或者一运行就报资源不足。把这些参数打出来后面写kernel时心里才有底。2. 核心API调用链拆解从平台到设备的完整流程2.1 clGetPlatformIDs先问系统有几个平台一切从clGetPlatformIDs开始。这个函数的典型用法是先调一次拿到平台数量再调一次拿到平台数组cl_uint platform_count 0; cl_int err clGetPlatformIDs(0, NULL, platform_count); if (err ! CL_SUCCESS) { // 平台枚举失败大概率是驱动层问题 return -1; } printf(platform count: %u\n, platform_count); cl_platform_id *platforms (cl_platform_id *)calloc(platform_count, sizeof(cl_platform_id)); err clGetPlatformIDs(platform_count, platforms, NULL);这个写法看起来有点多此一举为什么要查两次因为OpenCL的API设计遵循“先查询大小再填充数据”的惯例很多枚举类函数都这样后面clGetDeviceIDs也一样。在跨平台代码里这个两段式是标准姿势不要嫌麻烦直接传一个固定数组进去——万一平台上挂了好几个OpenCL实现数组大小不够就直接CL_INVALID_VALUE。注意第一次调用时num_entries传0、platforms传NULL但num_platforms必须是非空指针否则函数会返回CL_INVALID_VALUE。很多新手在这行上吃过亏以为返回的status不用接收结果后续全是野指针操作。2.2 clGetPlatformInfo平台级的元数据拿到platform_id之后用clGetPlatformInfo取平台名、厂商、版本等。这个函数有个容易让新手误用的地方param_value_size是以字节为单位的缓冲区大小而不是元素个数返回的字符串包含结尾的\0。所以缓冲区至少要比期望的字符串长1字节否则直接返回CL_INVALID_VALUE。char info[256]; err clGetPlatformInfo(platform_id, CL_PLATFORM_NAME, sizeof(info), info, NULL); if (err CL_SUCCESS) { printf(Platform Name: %s\n, info); }在RK3288上CL_PLATFORM_NAME通常显示为类似“ARM Platform”的字样CL_PLATFORM_VENDOR一般是“ARM”CL_PLATFORM_VERSION一般是“OpenCL 1.1”。不同固件的驱动字符串可能略有出入但这个平台名能帮你快速判断当前加载的用户态实现是谁家的。如果平台名显示得很奇怪比如空白或者乱码基本可以怀疑libOpenCL.so和GPU驱动不是一个版本的配套产物。2.3 clGetDeviceIDs从平台拿到设备平台是壳设备才是核心。clGetDeviceIDs的函数签名是这样的cl_int clGetDeviceIDs(cl_platform_id platform, cl_device_type device_type, cl_uint num_entries, cl_device_id *devices, cl_uint *num_devices);这里最需要注意的就是device_type参数。我建议把它当成两个独立查询来做先CL_DEVICE_TYPE_ALL枚举全部设备再单独用CL_DEVICE_TYPE_GPU查一次GPU设备。这么做不是为了炫技而是因为个别驱动在指定CL_DEVICE_TYPE_GPU时如果内部没有匹配项会返回CL_DEVICE_NOT_FOUND而不是退而求其次返回CPU设备反过来如果用CL_DEVICE_TYPE_ALL时驱动实现偷懒也有可能把不支持的设备类型混进来。另外拿到num_devices之后一定要清点一下数量再继续很多驱动在“0个设备”时也会返回CL_SUCCESS这时候如果直接按固定数量去填充指针后面就是未定义行为。2.4 clGetDeviceInfo设备能力摸底clGetDeviceInfo和clGetPlatformInfo的用法几乎一样只是第一个参数从platform_id换成了device_id。务必至少打印这几个CL_DEVICE_NAME看驱动报上来的设备名是不是Mali-T764CL_DEVICE_VENDOR确认实现来源CL_DEVICE_VERSION设备的OpenCL版本CL_DEVICE_MAX_COMPUTE_UNITS并行计算规模的关键指标CL_DEVICE_MAX_WORK_GROUP_SIZE每个work group的工作项上限CL_DEVICE_MAX_WORK_ITEM_SIZES各维度work item上限CL_DEVICE_GLOBAL_MEM_SIZE和CL_DEVICE_LOCAL_MEM_SIZECL_DEVICE_EXTENSIONS看是否有cl_khr_fp16等扩展CL_DEVICE_MAX_WORK_ITEM_SIZES返回的是一个size_t数组长度是3分别对应x、y、z三个维度。有些朋友只关心一维数据就只打印第0个元素结果后面设NDRange的时候在y维上翻车。这个参数最好和CL_DEVICE_MAX_WORK_GROUP_SIZE一起打印出来对照它们之间的约束关系在Mali驱动上经常让人意外。2.5 常用参数速查表下面这个表可以直接当速查卡用写探测程序时对着抄就行参数返回类型含义典型用途CL_PLATFORM_NAMEchar[]平台名确认运行时是谁提供的CL_PLATFORM_VENDORchar[]平台厂商辅助辨认实现CL_PLATFORM_VERSIONchar[]OpenCL版本判断能否用2.x接口CL_DEVICE_NAMEchar[]设备名确认GPU型号CL_DEVICE_VERSIONchar[]设备OpenCL版本能力边界CL_DEVICE_TYPEcl_device_type设备类型区分CPU/GPUCL_DEVICE_MAX_COMPUTE_UNITScl_uint计算单元数划分work groupCL_DEVICE_MAX_WORK_GROUP_SIZEsize_t工作组最大工作项数防止越界CL_DEVICE_MAX_WORK_ITEM_SIZESsize_t[3]各维度上限设置NDRangeCL_DEVICE_GLOBAL_MEM_SIZEcl_ulong全局内存大小数据规模预算CL_DEVICE_LOCAL_MEM_SIZEcl_ulong本地内存大小缓存分块依据CL_DEVICE_EXTENSIONSchar[]支持的扩展列表特性判断CL_DEVICE_AVAILABLEcl_bool设备是否可用排除显示占用的GPU3. 可直接编译运行的示例代码3.1 完整代码清单直接贴一份我在RK3288上跑通的代码一份文件搞定适合放在板上直接编译。逻辑很简单查询平台数量、遍历平台、遍历平台下所有设备然后把常用参数打出来。#include stdio.h #include stdlib.h #include CL/cl.h static void print_platform_info(cl_platform_id platform) { char buf[512]; printf( --- Platform ---\n); clGetPlatformInfo(platform, CL_PLATFORM_NAME, sizeof(buf), buf, NULL); printf( Name : %s\n, buf); clGetPlatformInfo(platform, CL_PLATFORM_VENDOR, sizeof(buf), buf, NULL); printf( Vendor : %s\n, buf); clGetPlatformInfo(platform, CL_PLATFORM_VERSION, sizeof(buf), buf, NULL); printf( Version : %s\n, buf); clGetPlatformInfo(platform, CL_PLATFORM_PROFILE, sizeof(buf), buf, NULL); printf( Profile : %s\n, buf); clGetPlatformInfo(platform, CL_PLATFORM_EXTENSIONS, sizeof(buf), buf, NULL); printf( Extensions : %s\n, buf); } static void print_device_info(cl_device_id device) { char buf[512]; cl_device_type type; cl_uint max_compute_units 0; size_t max_work_group_size 0; size_t work_item_sizes[3] {0, 0, 0}; cl_ulong global_mem 0, local_mem 0; const char *type_str UNKNOWN; printf( --- Device ---\n); clGetDeviceInfo(device, CL_DEVICE_TYPE, sizeof(type), type, NULL); if (type CL_DEVICE_TYPE_GPU) type_str GPU; else if (type CL_DEVICE_TYPE_CPU) type_str CPU; else if (type CL_DEVICE_TYPE_ACCELERATOR) type_str ACCELERATOR; printf( Type : %s\n, type_str); clGetDeviceInfo(device, CL_DEVICE_NAME, sizeof(buf), buf, NULL); printf( Name : %s\n, buf); clGetDeviceInfo(device, CL_DEVICE_VENDOR, sizeof(buf), buf, NULL); printf( Vendor : %s\n, buf); clGetDeviceInfo(device, CL_DEVICE_VERSION, sizeof(buf), buf, NULL); printf( Version : %s\n, buf); clGetDeviceInfo(device, CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(max_compute_units), max_compute_units, NULL); printf( Max CUs : %u\n, max_compute_units); clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(max_work_group_size), max_work_group_size, NULL); printf( Max WorkGroup : %zu\n, max_work_group_size); clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_ITEM_SIZES, sizeof(work_item_sizes), work_item_sizes, NULL); printf( Max WorkItem : %zu x %zu x %zu\n, work_item_sizes[0], work_item_sizes[1], work_item_sizes[2]); clGetDeviceInfo(device, CL_DEVICE_GLOBAL_MEM_SIZE, sizeof(global_mem), global_mem, NULL); printf( Global Mem : %llu bytes (%llu MB)\n, (unsigned long long)global_mem, (unsigned long long)(global_mem / (1024 * 1024))); clGetDeviceInfo(device, CL_DEVICE_LOCAL_MEM_SIZE, sizeof(local_mem), local_mem, NULL); printf( Local Mem : %llu bytes (%llu KB)\n, (unsigned long long)local_mem, (unsigned long long)(local_mem / 1024)); clGetDeviceInfo(device, CL_DEVICE_EXTENSIONS, sizeof(buf), buf, NULL); printf( Extensions : %s\n, buf); } int main(void) { cl_uint platform_count 0; cl_int err clGetPlatformIDs(0, NULL, platform_count); if (err ! CL_SUCCESS || platform_count 0) { fprintf(stderr, No OpenCL platform found, err%d\n, err); return -1; } cl_platform_id *platforms (cl_platform_id *)calloc(platform_count, sizeof(cl_platform_id)); if (!platforms) { return -1; } err clGetPlatformIDs(platform_count, platforms, NULL); if (err ! CL_SUCCESS) { free(platforms); fprintf(stderr, clGetPlatformIDs failed, err%d\n, err); return -1; } printf(Platform count: %u\n, platform_count); for (cl_uint i 0; i platform_count; i) { print_platform_info(platforms[i]); cl_uint device_count 0; clGetDeviceIDs(platforms[i], CL_DEVICE_TYPE_ALL, 0, NULL, device_count); if (device_count 0) { printf( (no devices found)\n); continue; } cl_device_id *devices (cl_device_id *)calloc(device_count, sizeof(cl_device_id)); if (!devices) { continue; } cl_int device_err clGetDeviceIDs(platforms[i], CL_DEVICE_TYPE_ALL, device_count, devices, NULL); if (device_err ! CL_SUCCESS) { free(devices); fprintf(stderr, clGetDeviceIDs failed, err%d\n, device_err); continue; } for (cl_uint j 0; j device_count; j) { print_device_info(devices[j]); } free(devices); } free(platforms); return 0; }代码里每个clGetPlatformInfo和clGetDeviceInfo其实都应该检查返回值我这里为了篇幅简化了实际写的时候建议保留err检查特别是扩展字段很可能因为缓冲区不够而返回CL_INVALID_VALUE。3.2 编译命令与库路径处理如果板子上跑的是带完整开发工具的Linux发行版直接在板子上编译最省事gcc -stdgnu99 -o cl_info cl_info.c -I/usr/include -lOpenCL如果用交叉编译工具链就需要指定SDK里libOpenCL.so所在目录比如aarch64-linux-gnu-gcc -stdgnu99 -o cl_info cl_info.c \ -I/path/to/sdk/sysroot/usr/include \ -L/path/to/sdk/target/lib \ -lOpenCL加-stdgnu99是为了让printf支持%zu这种C99格式符有些老工具链默认是gnu89编译时会报警告虽然不影响运行但看着难受。另外有些厂商SDK里没有libOpenCL.so这个名字而是直接提供libmali.so这时候建个软链接就行ln -s libmali.so libOpenCL.so链接期和运行期是两回事编译时找到- lOpenCL运行时也需要在动态库搜索路径里能找到它对应的真实文件缺一个环节都不行。3.3 部署到开发板与运行把编译好的cl_info拷贝到板子上加执行权限后直接跑chmod x cl_info ./cl_info如果报“error while loading shared libraries: libOpenCL.so: cannot open shared object file”说明运行时找不到库先看库在哪个目录find / -name libOpenCL.so* 2/dev/null找到之后把目录加进环境变量再跑export LD_LIBRARY_PATH/usr/lib:$LD_LIBRARY_PATH ./cl_info我在RK3288上遇到这个问题的次数不算少主要是SDK里预编译的OpenCL库路径和rootfs默认搜索路径不一致。用ldd也能快速确认依赖关系ldd ./cl_info这一条命令能把libOpenCL.so的实际解析路径看得清清楚楚省得瞎猜。3.4 RK3288实测输出长什么样下面是我手头某块板子上的输出固件和驱动版本都比较老数值仅供参考不同驱动版本会有差异Platform count: 1 --- Platform --- Name : ARM Platform Vendor : ARM Version : OpenCL 1.1 Profile : FULL_PROFILE Extensions : cl_khr_global_int32_base_atomics cl_khr_global_int32_extended_atomics --- Device --- Type : GPU Name : Mali-T764 Vendor : ARM Version : OpenCL 1.1 Max CUs : 4 Max WorkGroup : 256 Max WorkItem : 256 x 16 x 1 Global Mem : 2147483648 bytes (2048 MB) Local Mem : 32768 bytes (32 KB) Extensions : cl_khr_global_int32_base_atomics cl_khr_global_int32_extended_atomics注意实际值高度依赖固件和驱动。如果你拿到的输出里Max WorkGroup是384、Global Mem是3GB不要慌先核对驱动版本。从输出能读出不少信息OpenCL 1.1意味着2.0专属接口不能用4个CU意味着并行规模不大work group不能开太大32KB local memory意味着如果用local memory优化缓存分块要控制在这个范围内。4. 实测中踩过的坑与排查方法4.1 头文件找不到宿主机上直接编译ARM程序常见的报错是“/usr/include/CL/cl.h: No such file or directory”。原因是宿主机根目录的头文件和目标系统不是一套。用交叉工具链时要把-I指向工具链sysroot下的include目录而不是宿主机的/usr/include。如果SDK里连CL/cl.h都没提供直接去Khronos官网下载opencl-headers即可这个头文件本身就是跨平台的放到项目include目录下就行。个别SDK里的cl.h版本很旧连cl_platform_id这种最基本类型都定义得和现代版本不一致建议统一用新版头文件老库文件不受影响。4.2 链接失败找不到-lOpenCL链接时报cannot find -lOpenCL最常见的原因是库不在-L指定的路径里。先确认SDK的target目录下有没有libOpenCL.so或libmali.sofind /path/to/sdk -name libOpenCL.so* -o -name libmali.so*如果只有libmali.so建软链接后重新链接。如果库文件存在但仍找不到多半是-L路径写错了或者工具链的sysroot路径覆盖了-L。把编译命令改成完整路径直接指定库文件也是一种办法aarch64-linux-gnu-gcc -stdgnu99 -o cl_info cl_info.c \ /path/to/sdk/target/lib/libOpenCL.so这种方式虽然不优雅但能快速排除链接路径环境问题。4.3 运行时找不到动态库编译过了拷到板上跑却报找不到libOpenCL.so这个我在RK3288上遇到过至少三次。原因通常是rootfs里库的安装位置和/usr/lib、/lib这些默认搜索路径不一致。处理方式有两种要么export LD_LIBRARY_PATH要么把库放到/usr/lib下并运行ldconfig。另外提醒一点如果板子上既有/lib/libOpenCL.so又有/vendor/lib/libOpenCL.so以ldd解析到的实际路径为准。不同目录里的库可能版本不同选错会导致各种诡异的运行错误。4.4 枚举到0个平台或0个设备clGetPlatformIDs返回0个平台基本不是代码问题而是环境问题。按下面顺序排查用dmesg查看GPU驱动是否加载成功比如Mali相关日志确认libOpenCL.so文件存在且不是0字节如果系统用的是ICD机制检查/etc/OpenCL/vendors/目录下有没有对应的.icd文件ICD这个东西值得单独提一下。标准Linux上的OpenCL可以通过ICD加载多个实现但需要系统里存在.icd文件告诉运行时去哪找库。很多嵌入式平台并不走ICD直接把libOpenCL.so暴露给应用链接这种情况下如果系统同时装了桌面版的ICD运行时反而可能出现平台数变成0或2的怪现象。如果枚举到平台但设备数为0重点检查GPU驱动和显示子系统。RK3288上如果连HDMI都没接、显示控制器没初始化某些驱动版本下GPU设备节点可能不暴露。这种时候先用CL_DEVICE_TYPE_ALL看看有没有CPU设备能区分是“驱动完全没起来”还是“GPU节点没暴露”。4.5 打印出来的数值明显不对最常见的是格式化问题。size_t在32位工具链下是4字节在64位下是8字节统一用%zu打印最安全。cl_ulong用%llu打印或者先强转成unsigned long long再打否则会得到截断后的乱值。还有一种情况是CL_DEVICE_MAX_WORK_ITEM_SIZES打印出来全是0。检查一下是不是把参数类型写错了这个参数需要的是size_t[3]数组如果你传了一个cl_uint数组进去函数会因为param_value_size对不上而返回CL_INVALID_VALUE而你没有检查返回值最后打印的是未初始化的内存。缓冲区大小也常出问题。CL_DEVICE_EXTENSIONS在Mali上一般不长512字节足够但如果你想把这个探测程序也跑在桌面OpenCL实现上扩展列表可能超过1KB。稳妥做法是先调一次拿到实际大小再动态分配size_t ext_size 0; clGetDeviceInfo(device, CL_DEVICE_EXTENSIONS, 0, NULL, ext_size); char *ext (char *)malloc(ext_size); clGetDeviceInfo(device, CL_DEVICE_EXTENSIONS, ext_size, ext, NULL);这套“先查大小再分配”的写法同样适用于所有返回字符串或数组的Info函数。4.6 常见OpenCL错误码速查调试时把错误码打印出来能省很多事下面这几个是探测阶段最容易遇到的错误码含义常见触发场景CL_SUCCESS 0成功无CL_DEVICE_NOT_FOUND -1没有匹配的设备指定GPU但驱动没暴露节点CL_OUT_OF_RESOURCES -5资源不足内存拷太大缓冲区爆了CL_INVALID_VALUE -30参数无效传入空指针、缓冲太小CL_INVALID_DEVICE -33设备无效使用了已释放的device_idCL_PLATFORM_NOT_FOUND_KHR -1001没有平台ICD配置缺失库是空壳遇到负数错误码别只记数字在代码里写个错误码转字符串的小函数调试体验会好很多。这个习惯越早养成越好后面写kernel时错误码会更频繁地出现。5. 打印完信息之后GPU计算这条路才刚刚开始5.1 把探测代码扩展成能力自检脚本跑通一次不等于以后不用再跑。固件一升级、驱动一换设备参数就可能变。我后来把cl_info交叉编译好放进板子/usr/local/bin然后用一个简单脚本封装起来结果重定向到日志文件#!/bin/sh cl_info /tmp/cl_info.log 21 grep -E Name|Version|Global Mem|Local Mem /tmp/cl_info.log以后调其它OpenCL程序遇到问题第一件事就是跑一下这个脚本确认环境没变。这比反复翻固件升级记录靠谱得多。5.2 下一步从设备信息到第一个kernel环境确认之后下一步就是写一个最简单的向量加法kernel走一遍完整流程用检测到的platform和device创建context在context上创建command queue写一个只有几行的kernel源码用clCreateProgramWithSource编译创建buffer、设置kernel参数enqueue NDRange读回结果这里有一个RK3288上很现实的注意点Mali-T764这颗GPU同时承担显示合成工作。如果板子接着HDMI在跑界面你又让GPU跑一个特别大的kernel画面卡顿是正常的不是代码写错了。要么避开显示高峰期要么把kernel拆小一点要么干脆接受卡顿先把功能验证完。5.3 几个后知后觉的经验所有API的返回值都要检查哪怕你只是在“打印信息”。最浪费时间的场景就是没检查错误的前提下撸了好几百行代码最后定位问题发现是最开始的clGetDeviceIDs就已经失败了。OpenCL版本不是由芯片决定的而是由驱动决定的。同一颗RK3288换个驱动版本能力和行为就可能不一样。所以每次换固件后重新跑一次探测代码应该成为一种固定习惯。如果最终要把OpenCL集成进应用程序别把打印平台设备信息的代码放在性能敏感路径上启动时做一次就够了。打印本身不慢但每次启动都刷一堆信息到终端既不美观也容易掩盖真正的日志内容。最后分享一个小技巧在打印扩展列表时如果发现驱动没有暴露cl_khr_fp16而你又确实需要半精度计算不要硬刚。要么换支持该扩展的固件版本要么改用float在这个问题上花太多时间不值得。嵌入式平台上驱动能力边界就是你的能力边界提前接受它后面写代码会顺畅很多。我刚开始搞RK3288的OpenCL时以为写kernel才是难点后来发现环境探测和错误排查花的时间比写kernel还多。工具链、动态库、驱动这三样东西不捋顺kernel写得再漂亮也跑不起来。这篇基本把打印平台设备信息的路趟平了希望后来的人少走几步弯路。