OpenVX
OpenVX
openvx
1. 編譯
嘗試編譯openvx_sample,下載相關代碼。
下載的sample code直接使用make可以生成libopenvx.so。
使用python Build.py --os linux可以編譯sample code。
2. OpenVX使用流程
主要包含7個部分:
創建openvx上下文
vx_context context = vxCreateContext();
創建輸入、輸出圖像結點
vx_image input_rgb_image = vxCreateImage( context, width, height, VX_DF_IMAGE_RGB );
vx_image output_rgb_image = vxCreateImage( context, width, height, VX_DF_IMAGE_RGB );
創建graph
vx_graph graph = vxCreateGraph(context);
構建graph
vxScaleImageNode(graph, input_rgb_image, output_rgb_image, VX_INTERPOLATION_AREA)
驗證graph
vxVerifyGraph( graph );
真正運行graph
vxProcessGraph(graph);
釋放資源
vxReleaseContext(&context);
3. OpenVX中調用OpenCL代碼解析
1. vxCreateContext
一個平臺對就一個target,一個target包含多個kernel。
./sample/framework/vx_context.c中的變量定義了幾種target支持, c_model, opencl, openmp:
vx_char targetModules[][VX_MAX_TARGET_NAME] = {
"openvx-c_model",
#if defined(EXPERIMENTAL_USE_OPENCL)
"openvx-opencl",
#endif
#if defined(EXPERIMENTAL_USE_OPENMP)
"openvx-openmp"
#endif
};
以OpenCL為例,當用戶調用函數vxCreateContext(sample/framework/vx_context.c)時,其會調用函數ownLoadTarget (sample/framework/vx_target.c), 去dlopen打開libopenvx-opencl.so, 使用dlsym(mod, name)獲取vxTargetInit, vxTargetAddKernel(sample/targets/opencl/vx_interface.c)等opencl的相關函數句柄。
而在vxTargetAddKernel函數中,調用ownInitializeKernel(sample/framework/vx_kernel.c)加載了所有OpenCL實現的kernel函數。
在sample/targets/opencl目錄下的c文件定義了一些vx_cl_kernel_description_t box3x3_clkernel變量,包括box3x3_clkernel, gaussian3x3_clkernel, and_kernel等 ,這些kernel
opencl kernel結構:
包含vx_kernel_description_t還有一些其它屬性,它把function置為NULL,并提供了一個sourcepath變量用來存放opencl函數。
typedef struct _vx_cl_kernel_description_t {
vx_kernel_description_t description;
char sourcepath[VX_CL_MAX_PATH];
char kernelname[VX_MAX_KERNEL_NAME];
cl_program program[VX_CL_MAX_PLATFORMS];
cl_kernel kernels[VX_CL_MAX_PLATFORMS];
cl_uint num_kernels[VX_CL_MAX_PLATFORMS];
cl_int returns[VX_CL_MAX_PLATFORMS][VX_CL_MAX_DEVICES];
void *reserved; /* for additional data */
} vx_cl_kernel_description_t;
kernel結構:
typedef struct _vx_kernel_description_t {
/*! rief The vx_kernel_e enum */
vx_enum enumeration;
/*! rief The name that kernel will be used with ef vxGetKernelByName. */
vx_char name[VX_MAX_KERNEL_NAME];
/*! rief The pointer to the function to execute the kernel */
vx_kernel_f function;
/*! rief The pointer to the array of parameter descriptors */
vx_param_description_t *parameters;
/*! rief The number of paraemeters in the array. */
vx_uint32 numParams;
/*! rief The parameters validator */
vx_kernel_validate_f validate;
/*! rief The input validator (deprecated in openvx 1.1) */
void* input_validate;
/*! rief The output validator (deprecated in openvx 1.1) */
void* output_validate;
/*! rief The initialization function */
vx_kernel_initialize_f initialize;
/*! rief The deinitialization function */
vx_kernel_deinitialize_f deinitialize;
} vx_kernel_description_t;
可以看到目前雖然配置了一些參數,但OpenCL分為主機端代碼和device端代碼,device端代碼在kernel/opencl中,而host端代碼在哪呢?如何根據設置的參數去執行Host端代碼,從而執行device端代碼:
可以看到在vxTargetInit函數中,調用ownInitializeKernel初始化kernel時,判斷了kfunc是否為NULL,(kfunc == NULL ? vxclCallOpenCLKernel : kfunc)如果為NULL則使用vxclCallOpenCLKernel函數。
我們再看vxclCallOpenCLKernel函數,我們發現這個函數里有clSetKernelArg,clEnqueueNDRangeKernel等OpenCL的API函數,這個便是host-side的OpenCL代碼。
2. vxScaleImageNode
在sample/framework/vx_node_api.c中定義了所有提供的可用的OpenVX結點,包括vxScaleImageNode結點,通過如下方法創建Node:
vx_kernel kernel = vxGetKernelByEnum( context, VX_KERNEL_SCALE_IMAGE );
如果函數有兩種實現,那么按照優先級使用: opencl > openmp > c_model。(不對,感覺優先使用的是c_model的函數;實際是先找到opencl kernel,但找到之后并沒有停止查找,找到后面的c_model就會覆蓋掉前面的opencl kernel。不知道這兒是寫錯了,還是就是要優先使用c_model,代碼見sample/framework/vx_kernel.c中的vxGetKernelByEnum函數)
node的參數如何傳遞給kernel: 在vxCreateNodeByStructure中調用vxSetParameterByIndex將Node的參數傳遞kernel。
3. vxVerifyGraph
vx_graph.c會調用每一個結點的validator函數,包括inputValidator,outputValidator,確保構建的Graph可以跑通。
4. vxProcessGraph
vxProcessGraph函數調用vxExecuteGraph函數,在其中調用action = target->funcs.process(target, &node, 0, 1);,其中的funcs.process就是各個target的vxTargetProcess函數。
在vxTargetProcess中會調用nodes[n]->kernel->function,即我們事先定義的host-side端代碼,傳遞結點,參數,以及參數個數:
status = nodes[n]->kernel->function((vx_node)nodes[n],
(vx_reference *)nodes[n]->parameters,
nodes[n]->kernel->signature.num_parameters);
而我們的function,則主要負責內存管理,以及調用device端代碼。
幾種參數類型:
memory:
CL_MEM_OBJECT_BUFFER
CL_MEM_OBJECT_IMAGE2D
scalar:
VX_TYPE_SCALAR
threashold:
VX_TYPE_THRESHOLD
4. OpenVX中使用OpenCL的編譯問題
使用Makefile編譯出來的so默認是沒有opencl。
使用Build.py出來的so可以有opencl,但結點報錯:
Target[1] is not valid!
Target[2] is not valid!
LOG: [ status = -17 ] Node: org.khronos.openvx.color_convert: parameter[1] is not a valid type 1280!
在target.mak中對SYSDEFS添加EXPERIMENTAL_USE_OPENCL,可以編譯Opencl,但在運行時build opencl 代碼時報錯,可以將錯誤信息打印出來,發現找不到頭文件。
查看代碼,發現在sample/targets/opencl/vx_interface.c中需要如下兩個參數,VX_CL_INCLUDE_DIR是VX頭文件位置,VX_CL_SOURCE_DIR是CL源碼位置,在環境中可以配置這兩個參數:
char *vx_incs = getenv("VX_CL_INCLUDE_DIR");
char *cl_dirs = getenv("VX_CL_SOURCE_DIR");
/usr/include/features.h:367:12: fatal error: 'sys/cdefs.h' file not found
在cl編譯命令里(sample/targets/opencl/vx_interface.c)添加-I /usr/include/x86_64-linux-gnu/:
snprintf(cl_args, sizeof(cl_args), "-D VX_CL_KERNEL -I %s -I /usr/include/x86_64-linux-gnu/ -I %s %s %s", vx_incs, cl_dirs...
Linux gnu/stubs-32.h: No such file or directory
這是缺少32位的嵌入式C庫。在嵌入式開發環境配置時,也常遇到這個問題。sudo apt-get install libc6-dev-i386
fatal error: 'stddef.h' file not found
定位stddef.h, 在cl編譯命令里cl_args里添加-I /usr/include/linux/。
vx_khr_opencl.h和vx_api.h里有些類型進行了重定義:
不要在vx_khr_opencl.h里include vx_api.h。
histogram.cl仍然報錯,將histogram的kernel去掉,就可以成功編譯。
5. 使用OpenCL vx_not
使用c_model的VX_KERNEL_NOT可以正常運行,使用opencl的就會報如下錯誤:
clSetKernelArg: OpenCL error CL_INVALID_ARG_INDEX at vxclCallOpenCLKernel in /home/guru_ge/openvx/openvx_sample/sample/targets/opencl/vx_interface.c:639
clSetKernelArg: OpenCL error CL_INVALID_ARG_INDEX at vxclCallOpenCLKernel in /home/guru_ge/openvx/openvx_sample/sample/targets/opencl/vx_interface.c:639
clEnqueueNDRangeKernel: OpenCL error CL_INVALID_KERNEL_ARGS at vxclCallOpenCLKernel in /home/guru_ge/openvx/openvx_sample/sample/targets/opencl/vx_interface.c:724
嘗試自己寫host side的code。
寫完發現并在Load時就不通過,檢查原因,打開log信息,發現在自己實現的代碼中有個CL_ERROR_MSG找不到,直接注釋該行代碼,程序可以正常運行。但是得到的結果還是不對,全是黑色,好像是沒有將處理后的結果拷貝回來,導致結果全是0。
這是因為cl中提供兩種形式的表達,一個是image2d_t,一個是簡單的buffer,在vx_interface.c中編譯cl時,加上了CL_USE_LUMINANCE,使用的是image2d_t;而在編譯整個OpenVX時,沒有加上CL_USE_LUMINANCE,導致外面使用的是簡單的buffer,而一個image2d_t的參數如果使用buffer需要傳遞5個參數,所以導致最后設置參數時兩邊不一致出錯。修改concerto/target.mak在31行SYSDEFS里加上CL_USE_LUMINANCE就可以了。
雖然不報錯了,但是出來的結果居然是一條直線,而不是取反后的效果,很奇怪:
一條直線
難道是傳給opencl的圖像就不對?嘗試手動拷貝圖像數據。
嘗試學習opencl c 語法,修改代碼查看結果,發現openvx在實現opencl的時候not kernel時存在一些不規范的地方,可能這些問題在其它平臺可以運行,但到現在這個平臺上就不行了。
原來的kernel實現:
__kernel void vx_not(read_only image2d_t a, write_only image2d_t b) {
int2 coord = (get_global_id(0), get_global_id(1));
write_imageui(b, coord, ~read_imageui(a, nearest_clamp, coord));
}
首先我嘗試打印其像素坐標時,發現得到的x, y坐標總是相同的,這很奇怪,這也解釋了為什么結果只有一條直線,因為它只寫了x, y坐標相同的那些像素點的值。查看 API發現get_global_id返回的是size_t,所以要用(int)去顯示轉換一下,再打印時,發現坐標在不停的變換,變成正常的了。
再運行,得到的圖居然是一幅全白的圖,說明像素值還有問題。嘗試打印原像素值,與取反后的像素值,發現相加不是255,說明這里的取反操作也有問題。read_imageui返回的類型是uint4向量,我們取反時,得到的結果并不對,這里使用255直接相減,最后代碼如下所示:
__kernel void vx_not(read_only image2d_t a, write_only image2d_t b) {
int2 coord = (int2)(get_global_id(0), get_global_id(1));
write_imageui(b, coord, 255-read_imageui(a, nearest_clamp, coord));
}
得到的效果正確了,如下:
right_result
6. 實現OpenCL vx_scale
實現opencl scale報錯:
parameter[1] is an invalid dimension 640x240
傳遞的參數是(inputImg, outputImg, type),parameter[1]應該是輸出圖像,大小確實應該是640x240。
使用c_model中的outputvalidator就不報這個錯了,說明不能直接return VX_SUCCESS,可能validator中還需要做些其它的事情。
在validator中會記錄一些信息,以供后面verify時與實際傳入參數比對,所以不能直接返回SUCCESS:
ptr->type = VX_TYPE_IMAGE;
ptr->dim.image.format = VX_DF_IMAGE_U8;
ptr->dim.image.width = width;
ptr->dim.image.height = height;
然而現在又報如下錯誤:
clEnqueueNDRangeKernel: OpenCL error CL_INVALID_EVENT at vxclCallOpenCLKernel in /home/guru_ge/openvx/openvx_sample/sample/targets/opencl/vx_interface.c:725
clEnqueueReadImage: OpenCL error CL_INVALID_EVENT at vxclCallOpenCLKernel in /home/guru_ge/openvx/openvx_sample/sample/targets/opencl/vx_interface.c:793
為什么event會invalid呢?嘗試自己寫host-side代碼,不使用默認的。
自己的代碼報如下錯誤:
VX_ZONE_ERROR:[vxcl_platform_notifier:59] CL_OUT_OF_RESOURCES error executing CL_COMMAND_READ_IMAGE on GeForce GTX 1080 Ti (Device 0)
spec里解釋CL_OUT_OF_RESOURCES: if there is a failure to allocate resources required by the OpenCL implementation on the device.
這估計是使用c_model的validator導致沒有初始化cl_mem,嘗試使用cl validator。
在check scale node parameter時報如下錯誤:
LOG: [ status = -10 ] Node[3] org.khronos.openvx.image_scaling: parameter[2] failed input/bi validation!
這估計是Input validator里只允許Image類型,沒有判斷scalar類型。
所以validator要對每個參數逐一判斷,對于input參數,直接返回SUCCESS就可以了;而對output參數,還需要寫一些信息。
結果還是全黑的,在kernel中打印坐標發現也不對,查看代碼發現輸入的維度是輸入圖片的大小,這兒應該是輸出圖像的大小才對。
再運行還是黑色,發現在取坐標轉換時,沒有將float轉為int,導致有問題(所以類型要確保完全一致,不會替你做轉換)。修改后,可以正常運行。
__kernel void image_scaling(read_only image2d_t in,
write_only image2d_t out)
{
//從glob_id中獲取目標像素坐標
int2 coordinate = (int2)(get_global_id(0), get_global_id(1));
//計算歸一化浮點坐標
float2 normalizedCoordinate = convert_float2(coordinate) * (float2)(2, 2);
//根據歸一化坐標從原圖中讀取像素數據
uint4 colour = read_imageui(in, sampler, convert_int2(normalizedCoordinate));
//將像素數據寫入目標圖像
write_imageui(out, coordinate, colour);
}
實際比較,vx_not, vx_scale使用opencl, c_model實現時間對比:
opencl:
average time: 44099.857143 us
c_model:
average time: 68343.380952 us
7. vx debug print信息
程序中通過獲取VX_ZONE_MASK環境變量的值來設置Log級別,可以通過如下將所有級別信息都打開:
export VX_ZONE_MASK=fffff
一共有如下幾個級別,每個級別占int的一個bit位:
enum vx_debug_zone_e {
VX_ZONE_ERROR = 0, /*!< Used for most errors */
VX_ZONE_WARNING = 1, /*!< Used to warning developers of possible issues */
VX_ZONE_API = 2, /*!< Used to trace API calls and return values */
VX_ZONE_INFO = 3, /*!< Used to show run-time processing debug */
VX_ZONE_PERF = 4, /*!< Used to show performance information */
VX_ZONE_CONTEXT = 5,
VX_ZONE_OSAL = 6,
VX_ZONE_REFERENCE = 7,
VX_ZONE_ARRAY = 8,
VX_ZONE_IMAGE = 9,
VX_ZONE_SCALAR = 10,
VX_ZONE_KERNEL = 11,
VX_ZONE_GRAPH = 12,
VX_ZONE_NODE = 13,
VX_ZONE_PARAMETER = 14,
VX_ZONE_DELAY = 15,
VX_ZONE_TARGET = 16,
VX_ZONE_LOG = 17,
VX_ZONE_MAX = 32
};
Ref
AMD openvx實現:
https://github.com/GPUOpen-ProfessionalCompute-Libraries/amdovx-core
https://github.com/GPUOpen-ProfessionalCompute-Libraries/amdovx-modules
總結
- 上一篇: π 的定义(极限)
- 下一篇: T100 GR 报表常见知识点 (含套版