嵌入式程序优化(2)-neon内建函数讲解

1. neon intrinsics介绍

neon intrinsics 是官方提供的 neon内建函数,使开发者不再需要手动使用内嵌汇编也能够使用 neon指令 来优化程序。本来着重讲解此类内建函数的使用方法及简单的代码实例
本文也可用作neon内建函数的快速查找表

PS:本文默认读者们已经熟悉了neon指令的基本情况

2. neon数据类型

  • 普通向量:分64bit向量和128bit向量
  • 数组向量: 格式如:type{n}x{m}_{z}, n 为向量元素的宽度, m 为向量元素的个数, z 为向量的个数。一般用于解交织操作, 这种类型一般为一个结构体 请自行查阅资料(解交织)
    比如int8x8x3_t类型指的就是该变量中含有 3 个int8x8_t的向量,分别对应成员:int8x8x3_t.val[0], int8x8x3_t.val[1], int8x8x3_t.val[2]

3. neon内建函数

在讲解之前,这里先说明一下内建函数的命名套路,让读者们可以更快的理解内建函数

内建函数的命名规则通常如下:

  • vop:指的是neon操作
  • {n}or{lane}:其中两者是互斥的,n 代表的是支持输入标量,lane支持选择向量中的一个元素作为输入
  • {datatype}:代表处理元素的类型

注意:
1. 带n的函数都含有立即数,此时输入参数不能为变量,且该立即数有范围限制, 具体请查看手册
2. 带qd操作的函数一般只支持32x4跟64x2类型的输入和输出

笔者通过阅读 neon手册 并结合自己的理解将 neon内建函数 分为以下几个部分

  • 常用操作
  • 装载和存储操作
  • 转换类操作
  • 加减法类操作
  • 乘法类操作
  • 数据处理类操作
  • 偏移类操作
  • 排列类操作

3.1. 常用操作

1. vcreate 创建一个64bit的向量,输入为一个64位的标量

1 uint64_t vcreate_1;
2 int8x8_t vcreate_result;
3 vcreate_result = vcreate_s8(vcreate_1);

2. vdup_n 将标量赋值到向量每一个元素中, vdupq_n支持q寄存器操作, 下面的vmov_n作用同vdup_n

1 int8_t vdup_1;
2 int8x8_t vdup_result;
3 vdup_result = vdup_n_s8(vdup_1);

3. vdup_lane 将输入向量中的每一个元素复制到目标向量的所有元素中

1 int8x8_t vdup_lane_1;
2 int8x8_t vdup_lane_reuslt;
3 int index = 1;//下标范围从0-7
4 vdup_lane_reuslt = vdup_lane_s8(vdup_lane_1, index);

3.2. 装载和存储操作

1. vldn 将内存中的数据按n路解交织存入向量中, 比如 按2路解交织, 有数据data[0], data[1], data[2], data[3], 那么存放时data[0],data[2] 存放在vld2_result.val[0]中, data[1],data[3] 存放在vld2_result.val[1]中

1 /* 
2     vldn注意事项, 只有n为 1 时结果才能定义为type{n}x{m}_t类型, 即普通向量, 
3     当 n 大于 1 时,此时的载入操作都是解交织的, 所以需要定义为数组向量 
4 */
5 int8_t vldn_data[4] = {0, 1, 2, 3};
6 int8x8x2_t vld2_result;
7 vld2_result = vld2(vldn_data);

2. vldn_lane 将内存中的数据按n路解交织存入到向量的某一个元素中

1 int8_t vldn_lane_data[4] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
2 int8x8x3_t vld3_lane_result;
3 int8x8x3_t vld3_lane_1;
4 int vld3_lane_index = 4;
5 vld3_lane_result=vld3_lane_s8(vldn_lane_data,vld3_lane_1,vld3_lane_index);

3. 将vldn_lane_data按顺序取出 3 个元素存入向量数组vld3_lane_1中的下标为vld3_lane_rindex的元素, reslut是未加载的元素将按原样返回的结果。

4. vldn_dup, 将内存中的数据拷贝到每一个向量中

1 int8_t vldn_dup_data[4] = {0, 1, 2, 3};
2 int8x8x4_t vld4_dup_result;
3 vldn_lane_data = vld4_dup_s8(vldn_dup_data);

5. vstn 将向量的值按 n 路交织存放到内存中, 比如有int8x8x3_t的数组向量, 那么将按照int8x8x3_t.val[0][0], int8x8x3_t.val[1][0], int8x8x3_t.val[2][0], int8x8x3_t.val[0][1], int8x8x3_t.val[1][1], int8x8x3_t.val[2][1]这样的顺序存入内存

int8x8x3_t vst3_1;
int8_t vst3_data[6] = {0};
vst3_s8(vst3_data, vst3_1);

6. vstn_lane, 将向量中某个通道的元素存入内存中, 比如int8x8x4_t

1     /* 除了跟vldn一样需要注意类型的定义外,还需要知道vstn是无返回的, 即为void */
2     int8x8x4_t vst4_1;
3     int8_t vst4_data[6] = {0};
4     vst3_lane_s8(vst4_data, vst4_1, 2);  

7. vset_lane 设置向量中某个通道的元素的值, 并将新的向量返回到结果中

1     int8x8_t vset_lane_result;
2     int8x8_t vset_lane_1;
3     int8_t new_val;
4     int vset_index = 3;
5     /*
6         将向量vset_lane_1中下标为vset_index的值设置为new_val, 
7         并将设置了新值的新向量返回到vset_lane_result中
8     */
9     vset_lane_result = vset_lane_s8(new_val, vset_lane_1, vset_index);

8. vget_lane 获取向量中某个通道的值, 并将新的向量返回到结果中

1     int8x8_t vget_lane_1;
2     int8_t vget_lane_result;
3     int vget_index;
4     /* 将向量vget_lane_1中下标为vget_index的值赋给vget_lane_result */
5     vget_lane_result = vget_lane_s8(vget_lane_1, vget_index);

3.3. 转换类操作

1. vreinterpret_type 向量转换, v指向量 re指再一次, interpret是解释,也就是将向量再进行一次解释, 如果后面加 q, 则表示在 q寄存器 中进行操作

    uint16x4_t vector_uint16x4;
    int16x4_t vector_int16x4;
    vector_uint16x4 = vreinterpret_u16_s16(vector_int16x4);

2. vcombine_type 向量连接,将 2 个 64bit的 d寄存器向量 连接为一个 128bit 的 q寄存器向量,被连接的2个向量必须是相同类型的向量, 格式为 vcombine_type

    int16x4_t top_int16x4;
    int16x4_t bottom_int16x4;
    int16x8_t result;
    result = vcombine_s16(bottom_int16x4, top_int16x4);

3./vget_high_type 获取 q寄存器向量高部分内容到 d寄存器向量中, vget_low_type同理

    int16x4_t vgh_int16x4;
    int16x8_t vgh_int16x8;
    vgh_int16x4 = vget_high_s16(vgh_int16x8);

3.4. 加减法类操作

1. 加法操作 vadd_type常规加法,vaddl_type支撑长整型操作, vaddw_type支持宽整型操作, vhadd_type将结果减半, vrhadd_type将结果减半后舍入(因为减半后会带入小数)

2. vaddq_type, 和vadd_type类似,只是使用的寄存器不同. vaddhn_type支持窄型计算,将四字节加法转换为双字节结果, 该计算会返回四字节的上半部分, vraddhn_type支持结果舍入

3. 减法同理

    int16x4_t add_1;
    int16x4_t add_2;
    int16x4_t add_result;
    add_result = vadd_s16(add_1, add_2);

3.5. 乘法类操作

注意:在做neon乘法指令的时候会有大约2个clock的阻塞时间,如果你要立即使用乘法的结果,则就会阻塞在这里,在写neon指令的时候需要特别注意。乘法的结果不能立即使用,可以将一些其他的操作插入到乘法后面而不会有时间的消耗。

1. vmul为常规乘法, vmull支持长整型操作, vmla支持结果累加, vmlal支持长整型运算, vmls支持结果累减(同vmla_type一样), vmlsl是vmls支持长整型操作的变体

2. vqdmulh, 将相乘结果加倍,然后返回结果的高半部分并截断高半部分, 带q表示饱和操作, vqdmulhq支持q寄存器操作, vqrdmulh返回结果不截断而是进行舍入

3. vqdmlal, 支持长整型操作, 将第三个向量和第二个向量相乘,并与第一个向量累加返回结果,结果支持饱和操作, vqdmlsl同, 只是改为相乘结果累减

4. vqdmull, 第一个q为饱和操作, d代表结果加倍, 最后的l代表支持长整型操作

5. vmla_lane_type, lane代表支持下标操作, 参数n是下标,表明第三个向量中,取出下标为n的标量, 并将其与向量二中的每一个向量相乘,将结果和向量一累加

6. vmlal_lan_type支持长整型操作, vqdmlal_lan_type支持饱和、结果加倍和长整型操作, vmls_lane_type系列同理

7. vmul_n_type 即使用标量相乘和向量中的每一个元素相乘, 其余函数类似仅支持的操作不同

    int16x4_t mul_1;
    int16x4_t mul_2;
    int16x4_t mul_3;
    int16x4_t mul_result;
    mul_result = vmul_s16(mul_1, mul_2);
    /* mul_2和mul_3相乘的结果和mul_1的向量相加 */
    mul_result = vmla_s16(mul_1, mul_2, mul_3);

3.6. 数据处理类操作

1. vpadd, 这里p是pair的意思,也就是一对,意思就是向量内部相邻的一对元素进行相加, 也就是4个元素的向量输出2个元素,所以这里需要输入 2 个向量,比如2个输入向量都是4个元素,那么每个输入向量输出2个元素,那么2个输入向量最终输出的是4个元素

2. vpaddl跟vpadd相同,但支持长整型操作

3. vpadal, 后面的a表示累加,比如第二个向量为8x8,那么进行padd操作后输出8x4,l代表8x4的结果宽度拓展为2倍变成16x4,a表示将16x4的结果与第一个向量相加

    int16x4_t result;
    int8x8_t vpaddl_1;
    result = vpaddl_s8(vpaddl_1);

4. vpmax, p代表相邻向量, 将向量内部的相邻元素进行比较, 输出较大的元素, vpmin同理

    int16x4_t result;
    int16x4_t vpmax_1;
    int16x4_t vpmax_2;
    result = vpaddl_s8(vpmax_1, vpmax_2);

5. vpmax, p代表相邻向量, 将向量内部的相邻元素进行比较, 输出较大的元素, vpmin同理

    int16x4_t result;
    int16x4_t vpmax_1;
    int16x4_t vpmax_2;
    result = vpaddl_s8(vpmax_1, vpmax_2);  

6. vabd, 向量对应的元素绝对值相加, vabdq支持q寄存器,vabdl支持长整型操作

7. vada跟vabd一样但支持结果累加, vabal支持累加和长整型操作

    int16x4_t result;
    int16x4_t vabd_1;
    int16x4_t vabd_2;    
    result = vabd_s16(vpmax_1, vpmax_2);  

8. vmax, 向量对应的元素进行比较,输出较大的元素, vmin同理

    int16x4_t vmax_result;
    int16x4_t vmax_1;
    int16x4_t vmax_2;    
    resulvmax_resultt = vabd_s16(vmax_1, vmax_2);  

9. vabs向量内部元素绝对值化, vqabs支持饱和操作

    int16x4_t vabs_result;
    int16x4_t vabs_1;
    vabs_result = vabs_s16(vabs_1);

10. vneg, 向量内部元素取反, vqneg支持饱和操作

    int16x4_t vneg_result;
    int16x4_t vneg_1;
    vneg_result = vneg_s16(vneg_1);

11. vcls对向量内的元素进行以下操作, 从最高位开始的连续的多个bit进行计数, 如果某一个bit和最高位相同,则计数加1, 计数时不算最高位. 比如0b10110101为0, 0b11110101为3

12. vclz从最高位开始对连续bit进行计数,如果bit为0, 则计数加1,计数时最高位参与计算

13. vcnt计算向量内部每个元素有多少bit位1

    int16x4_t vcls_result;
    int16x4_t vcls_1;
    int16x4_t vcls_2;
    vcls_result = vneg_s16(vcls_1, vcls_2);

14. vrecpe对向量内部每个元素求近似导数, 近支持32x2类型的向量

    uint32x2_t vrecpe_result;
    uint32x2_t vrecpe_1;
    vrecpe_result = vrecpe_u32(vrecpe_1); 

15. vrecps对向量内部的元素进行newton-raphson计算求出倒数

  float32x2_t vrecps_result;
  float32x2_t vrecps_1;
  float32x2_t vrecps_2;
  vrecps_result = vrecpe_f32(vrecps_1, vrecps_2);    

16. vrsqrte对向量内部的元素求平方根倒数

    float32x2_t vrsqrte_result;
    float32x2_t vrsqrte_1;
    vrsqrte_result = vrecpe_f32(vrsqrte_1); 

17. vrsqrts对向量内部的元素进行newton-raphson计算求出倒数

    float32x2_t vrsqrts_result;
    float32x2_t vrsqrts_1;
    float32x2_t vrsqrts_2;
    vrecps_result = vrecpe_f32(vrecps_1, vrecps_2);  

18. vmovn 将q寄存器中的高部分赋值到结果中, 可以看出该操作是窄型操作, vqmovn支持饱和操作, vqmovun中的u是指可以将符号类型的输入向量计算出无符号类型的结果

  int8x8_t vmovn_result;
  int16x8_t vmovn_1;  
  vmovn_result = vmovn_s16(vmovn_1);

  uint8x8_t vqmovun_result;
  int16x8_t vqmovun_1; 
  vqmovun_result = vqmovun_s16(vmovn_1);

19. vmovl 将d寄存器中的元素宽度扩展为原来的2倍,并将结果存入q寄存器中, 可以看出该操作是长整型型操作

    int16x8_t vmovl_result;
    int8x8_t vmovl_1;
    vmovl_result = vmovn_s8(vmovl_1); 

3.7. 偏移类操作

1. vshl 将输入向量(第一个向量)按偏移向量(第二个向量)中的每一个元素的值向左进行偏移, 每个元素中从左侧移出的位将丢失, 偏移向量按照lsb排序并且是有符号的,如果偏移向量中的元素为负值, 那么将向右进行偏移

2. vqshl 支持饱和操作

3. vrshl 支持位移后对数值进行舍入取整数

4. vqrshl 同时支持舍入和饱和操作

5. vshl_n 将输入的标量作为偏移量, 对向量中的每一个元素进行坐移

6. vqshl_n 作用同vshl_n, 支持饱和操作

7. vqshlu_n 作用同vqshl_n, 支持将有符号数转换为无符号数 (u代表可以将输入的有符号数在进行运算后输出为无操作数)

8. vshll_n 作用同vshl_n, 支持长整型操作

    int8x8_t vshl_input;
    int8x8_t vshl_offset;
    int8x8_t vshl_result;
    vshl_result = vshl_s8(vshl_input, vshl_offset);

9. vshr_n 将输入的标量作为偏移量, 对向量中的每一个元素进行右移

10. vrshr_n 作用同vshr_n,支持将偏移后的元素的值进行舍入

11. vshrn_n 作用同vshr_n, 支持窄型操作,即将q寄存器类型的向量转换为d寄存器类型的向量

12. vqshrun_n 是在vshr_n的基础上, 支持饱和, 有符号转无符号以及窄型操作

13. vqrshrun_n 在vqshrun_n基础上支持将结果进行舍入

    int8_t offset = 2;
    int8x8_t vshr_n_input;
    int8x8_t vshr_n_result;
    vshr_n_result = vshr_n_s8(vshr_n_input, offset);

14. vsra_n a表示累加, r表示右移, n表示立即数, 将 2 个输入向量的每一个元素都按照立即数n进行右移, 再将右移后的结果累加到结果向量中

15. vrsra_n 支持将累加后的结果进行舍入

    int8x8_t vsra_n_input_1;
    int8x8_t vsra_n_input_2;
    int8x8_t vsra_n_result;
    int offest = 2;
    vsra_n_result = vsra_n_s8(vsra_n_input_1, vsra_n_input_2, offset);

16. vsri_n 第一个输入向量可以保存原来的目标向量。将第二个向量的每个元素按照立即数n进行右移, 将右移后的数据与目标向量中的数据进行插入合并, 即右移后的向量元素插入时不会影响目标寄存器中元素的最高n个有效位

17. vsli_n 第一个输入向量可以保存原来的目标向量。将第二个向量的每个元素按照立即数n进行左移, 将左移后的数据与目标向量中的数据进行插入合并, 即左移后的向量元素插入时不会影响目标寄存器中元素的最低n个有效位

  int8x8_t vsri_n_result;
  int8x8_t vsri_n_input1;
  int8x8_t vsri_n_input2;
  int offset = 2;
  /*
  此处立即数n为2, 
  则有(vsri_n_result[0] & 0b11000000) | (vsri_n_input2[0] & 0b00111111)
  */
  vsri_n_result = vsli_n_s8(vsri_n_input1, vsri_n_input2, offset);

18. vceq 比较第一个输入向量与第二输入向量,然后将比较结果放在对应结果向量的对应元素,相等为 1 , 不相等为 0

19. vcge 比较第一个输入向量与第二输入向量,然后将比较结果放在对应结果向量的对应元素,如果第一个向量元素大于等于第二个向量,则结果为 1, 否则为 0

20. vcle 比较第一个输入向量与第二输入向量,然后将比较结果放在对应结果向量的对应元素,如果第一个向量元素小于等于第二个向量,则结果为 1, 否则为 0

21. vcgt 比较第一个输入向量与第二输入向量,然后将比较结果放在对应结果向量的对应元素,如果第一个向量元素大于第二个向量,则结果为 1, 否则为 0

22. vclt 比较第一个输入向量与第二输入向量,然后将比较结果放在对应结果向量的对应元素,如果第一个向量元素小于第二个向量,则结果为 1, 否则为 0

23. vcage 与vcge类似,但比较的值是向量元素的绝对值

24. vcale 与vcle类似,但比较的值是向量元素的绝对值

25. vcagt 与vcgt类似,但比较的值是向量元素的绝对值

26. vcalt 与vclt类似,但比较的值是向量元素的绝对值

    int8x8_t vceq_input1;
    int8x8_t vceq_input2;
    int8x8_t vceq_result;
    vceq_result = vceq_s8(vceq_input1, vceq_input2);

27. vtst 按位与运算逻辑, 将两个向量的元素按位进行与逻辑运算,如果对应元素的运算结果不为 0, 则目的向量中的对应元素置为 全1, 否则置为 全0

    int8x8_t vtst_input1;
    int8x8_t vtst_input2;
    int8x8_t vtst_result;
    vtst_result = vtst_s8(vtst_input1, vtst_input2);

30. vmvn对输入向量中的每个元素进行按位反转

    int8x8_t vmvn_input;
    int8x8_t vmvn_result;
    vmvn_result = vmvn_s8(vmvn_input);

31. vand在输入向量的相应元素之间执行按位与运算

32. vorr在输入向量的相应元素之间执行按位或运算

33. veor在输入向量的相应元素之间执行按位异或运算

34. vbic对第一个向量中的元素进行按位清除。 要清除的位是在第二向量的元素中设置的位

35. vorn在第一个向量的元素与第二个向量的元素的补码之间执行按位“或”运算。

    int8x8_t vand_input1;
    int8x8_t vand_input2;
    int8x8_t vand_result;
    vand_result = vand_s8(vand_input1, vand_input2);

**36. **vbsl 有 3 个输入向量,如果第三个向量中的一个元素中的某一个bit为0, 则选择第一个向量中的对应元素的对应bit到目标向量中的对应元素对应bit中, 如果为0, 则从第二个向量中选择的对应元素的对应bit赋给目标向量中的对应元素的对应bit

    int8x8_t vbsl_input1;
    int8x8_t vbsl_input2;
    int8x8_t vbsl_input3;
    int8x8_t vbsl_result;
    vbsl_result = vand_s8(vbsl_input1, vbsl_input1, vbsl_input2, vbsl_input3);

3.8. 排列类操作

1. vext 从第二个元素的低端提取n 个元素放在目标向量的高端, 在第一个元素的高端提取剩余的元素放在目标向量中的低端

    int n = 5;
    int8x8_t vext_input1;
    int8x8_t vext_input2;
    int8x8_t vext_result;
    vext_result = vext_s8(vext_input1, vext_input2, n);

2. vtbln 该指令用于查表赋值, 一共有 2 个输入向量, 第二个输入向量的元素是作为索引, 第一个输入向量作为查找表, 该指令会在使用索引然后在查找表中找到指定索引的元素,然后将找到的元素赋值给结果向量中的元素(与索引所在的元素对应, 假设索引是在第一个元素,则存放在结果向量的第一个元素)
n范围为[0, 4] 其表示的是使用的d寄存器数量,当n大于1时, 第一个参数使用的是数组向量, 如果索引超过查找表范围, 则返回0

3. vtbln:与 vtbln 一样, 但在超出范围时结果向量中的元素不会变为0, 而是保持不变

  char table[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
  char index[8] = {0, 1, 2, 3, 4, 5, 6, 7};
  int8x8x2_t vtbl2_input1;
  int8x8_t vtbl2_input2;
  int8x8_t vtbl2_result;
  vtbl2_input1 = vld2_dup_s8(table);//将查找表解交织装载到向量中
  vtbl2_input2 = vld1_dup_s8(index);
  vtbl2_result = vtbl2_s8(vtbl2_input1, vtbl2_input2);

4. vrev64 以32bit为一个单位, 反转向量的一个单位内指定类型元素的顺序, 并将结果放入相应的目标向量中. 支持的指定类型元素有8bit, 16bit和32bit. 注意: 反转的是元素在向量内部的顺序,不是元素bit的顺序

5. vrev32与vrev64类似, 但只支持8位和16位

6. vrev16与vrev64类似, 但只支持8位

  int8x8_t vrev64_input;
  int8x8_t vrev64_result;
  vrev64_result = vrev64_s8(vrev64_input);

7. vtrn将其输入向量的元素视为2 x 2矩阵的元素,并将其转置。 本质上,它将第一个输入向量中的奇数索引的元素与第二个输入向量中的偶数索引的元素交换。

 
trn示意.png
    int8x8_t vtrn_input1;
    int8x8_t vtrn_input2;
    int8x8x2_t vtrn_result;
    vtrn_result = vtrn_s8(vtrn_input1, vtrn_input2);

8. vzip 将 2 个向量进行交织存放
9. vuzp是 vzip 的方向过程

    int8x8_t vzip_input1;
    int8x8_t vzip_input2;
    int8x8x2_t vzip_result;
    vzip_result = vzip_s8(vzip_input1, vzip_input2);
    return 0;

4. 代码实例

笔者使用neon内建函数写了几个示例代码,可供各位读者理解
完整代码在笔者的github上:https://github.com/wipping/neon,欢迎各位下载

int32_t neon_intrinsics_matrixMul_float4x4(const float* matrix_left, float* matrix_right, float* matrix_result)
{
    if(NULL == matrix_left)
    {
        return -1;
    }   
    if(NULL == matrix_right)
    {
        return -1;
    }    
    if(NULL == matrix_result)
    {
        return -1;
    } 

    int offset = 4;
    int result_addr_offset = 0;
    int i = 0;
    int j = 0;
    float32x4_t matrix_right_row[4];
    float32x4_t matrix_left_row;
    float32x4_t result;

    for(i = 0; i < 4; i++)
    {
        matrix_right_row[i] = vld1q_f32(matrix_right + offset * i);
    }    

    for(j = 0; j < 4; j++)
    {
        matrix_left_row = vld1q_f32(matrix_left + j * offset);
        result = vdupq_n_f32(0);
        for(i = 0; i < 4; i++)
        {
            switch(i)
            {
                case 0:
                    result = vmlaq_lane_f32(result, matrix_right_row[i], vget_low_f32(matrix_left_row), 0);
                    break;
                case 1:
                    result = vmlaq_lane_f32(result, matrix_right_row[i], vget_low_f32(matrix_left_row), 1);
                    break;
                case 2:
                    result = vmlaq_lane_f32(result, matrix_right_row[i], vget_high_f32(matrix_left_row), 0);
                    break;
                case 3:
                    result = vmlaq_lane_f32(result, matrix_right_row[i], vget_high_f32(matrix_left_row), 1);
                    break;
            }
        }

        vst1q_f32(matrix_result, result);
        matrix_result = matrix_result + offset;
    }  
    return 0;
}

int32_t neon_intrinsics_matrixMul_float3x3(const float* matrix_left, float* matrix_right, float* matrix_result)
{
    if(NULL == matrix_left)
    {
        return -1;
    }   
    if(NULL == matrix_right)
    {
        return -1;
    }    
    if(NULL == matrix_result)
    {
        return -1;
    } 

    int offset = 3;
    int left_addr_offset = 0;
    int result_addr_offset = 0;
    int i = 0;
    int j = 0;
    float right_tmp[4] = {0};
    float left_tmp[4] = {0};
    float result_tmp[4] = {0};
    float32x4_t matrix_right_row[4];
    float32x4_t matrix_left_row;
    float32x4_t result;

    for(i = 0; i < 3; i++)
    {
        bzero(right_tmp, sizeof(float) * 4);
        memcpy(right_tmp, matrix_right + i * offset, sizeof(float) * offset);
        matrix_right_row[i] = vld1q_f32(right_tmp);
    }    

    for(j = 0; j < 3; j++)
    {
        bzero(left_tmp, sizeof(float) * 4);
        memcpy(left_tmp, matrix_left + j * offset, sizeof(float) * offset);
        matrix_left_row = vld1q_f32(left_tmp);
        result = vdupq_n_f32(0);
        for(i = 0; i < 4; i++)
        {
            switch(i)
            {
                case 0:
                    result = vmlaq_lane_f32(result, matrix_right_row[i], vget_low_f32(matrix_left_row), 0);
                    break;
                case 1:
                    result = vmlaq_lane_f32(result, matrix_right_row[i], vget_low_f32(matrix_left_row), 1);
                    break;
                case 2:
                    result = vmlaq_lane_f32(result, matrix_right_row[i], vget_high_f32(matrix_left_row), 0);
                    break;
                case 3:
                    result = vmlaq_lane_f32(result, matrix_right_row[i], vget_high_f32(matrix_left_row), 1);
                    break;
            }
        }
        bzero(result_tmp, sizeof(float) * 4);
        vst1q_f32(result_tmp, result);
        memcpy(matrix_result, result_tmp, sizeof(float) * offset);
        matrix_result = matrix_result + offset;
    }  
    return 0;
}

int neon_intrinsics_rgb888Tobgr888(uint8_t* image_src, uint8_t* image_dst, uint32_t pixel_num)
{

    uint8_t *src = image_src;
    uint8_t *dst = image_dst;
    int count = pixel_num;
    uint8_t bit_mask = 0xff;
    uint8x16x3_t vsrc;
    uint8x16x3_t vdst;
    uint8x16_t add_tmp;
    uint8x16_t tmp;
    uint8x16_t mask;

    
    /* 注意: 仅支持像素个数8对齐的图像 */
    mask = vdupq_n_u8(bit_mask);
    add_tmp = vdupq_n_u8(0);
    while (count >= 8) 
    {
        vsrc = vld3q_u8(src);//装载源数据
        tmp = vdupq_n_u8(0);

        //vswp无内建函数 使用其他方法实现
        tmp = vaddq_u8(vsrc.val[0], add_tmp);

        vsrc.val[0] = vbicq_u8(vsrc.val[0], mask);
        vsrc.val[0] = vaddq_u8(vsrc.val[2], add_tmp);

        vsrc.val[2] = vbicq_u8(vsrc.val[2], mask);
        vsrc.val[2] = vaddq_u8(tmp, add_tmp);

        // /* 循环 */
        vst3q_u8(dst, vsrc);
        dst += 8*3;
        src += 8*3;
        count -= 8;
    }

    return 0;
}

int neon_intrinsics_rgb565Torgb888(uint16_t* image_src, uint8_t* image_dst, uint32_t pixel_num)
{
    uint16_t *src = image_src;
    uint8_t *dst = image_dst;
    int count = pixel_num;
    uint16x8_t vsrc;
    uint8x8x3_t vdst;

    /* 注意: 仅支持像素个数8对齐的图像 */
    while (count >= 8) 
    {
        vsrc = vld1q_u16(src);//装载源数据
        /*  
            注意: rgb565转rgb88因为通道的bit位宽不同,所以需要使用低位进行补偿, 具体请自行查阅
            1. 使用vreinterpretq_u8_u16将源向量中的16bit元素转为8bit元素
            2. 使用vshrq_n将8bit元素作移 5 位, 这样即可将位于高位的红色数据从5bit转为8bi
            3. 将vreinterpretq_u16_u8 将8bit数据转换为16bit, 此时高8bit为红色通道数据
            4. 然后在使用vshrn_n_u16向右移动8个bit, 此时低8bit为红色数据, 并因为窄型数据操作的原因, 所以可以将16bit数据移动并转换为8bit
        */
        vdst.val[0] = vshrn_n_u16(vreinterpretq_u16_u8(vshrq_n_u8(vreinterpretq_u8_u16(vsrc), 3)), 5);
        /* 使用支持窄型数据操作的右移指令vshrn_n_u16, 将绿色通道的数据移动的低8位, 此时需要清空非绿色通道的数据并且进行补偿, 所以需要使用vshl_n_u8向左移动2bit, 移动时因为是窄型操作, 所以数据从16bit变为8bit, 从而提取出绿色通道数据 */
        vdst.val[1] = vshl_n_u8(vshrn_n_u16(vsrc, 5), 2);
        /* 先使用vshlq_n_u16将数据向左移动2bit, 然后使用窄型移动指令, 所以数据从16bit变为8bit, 从而提取出蓝色通道数据 */
        vdst.val[2] = vmovn_u16(vshlq_n_u16(vsrc, 3));
        /* 循环 */
        vst3_u8(dst, vdst);
        dst+= 8*3;
        src += 8;
        count -= 8;
    }
}

作者:wipping的技术小栈
链接:https://www.jianshu.com/p/3212bdb50469
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

posted on 2022-04-13 16:47  feng..liu  阅读(2210)  评论(0编辑  收藏  举报