从0开始学习NEON(1)

1、前言

在上个博客中对NEON有了基础的了解,本文将针对一个图像下采样的例子对NEON进行学习。

学习链接:CPU优化技术 - NEON 开发进阶

上文链接:https://blog.csdn.net/weixin_42108183/article/details/136412104

2、第一个例子

现在有一张图片,需要对UV通道的数据进行下采样,对于同种类型的数据,相邻的4个元素求和并求均值。示意图如下图所示:

在这里插入图片描述

假定图像数据的宽为16的整数倍,如果使用c++代码,可以写出下面的代码:

void DownscaleUv(uint8_t *src, uint8_t *dst, int32_t src_stride, int32_t dst_width, int32_t dst_height, int32_t dst_stride)
{
    //遍历每一行的数据
    for (int32_t j = 0; j < dst_height; j++)
    {	
        // 偶数行起始位置,
        uint8_t *src_ptr0 = src + src_stride * j * 2;
        // 奇数行起始位置
        uint8_t *src_ptr1 = src_ptr0 + src_stride;
        // 存储起始位置
        uint8_t *dst_ptr = dst + dst_stride * j;
		// 没一次循环计算没
        for (int32_t i = 0; i < dst_width; i += 2)
        {
            // U通道 (u1 + u2 + u3 + u4) / 4
            dst_ptr[i] = (src_ptr0[i * 2] + src_ptr0[i * 2 + 2] +
                         src_ptr1[i * 2] + src_ptr1[i * 2 + 2]) / 4;
            // V通道 (v1 + v2 + v3 + v4) / 4
            dst_ptr[i + 1] = (src_ptr0[i * 2 + 1] + src_ptr0[i * 2 + 3] +
                             src_ptr1[i * 2 + 1] + src_ptr1[i * 2 + 3]) / 4;
        }
    }
}

通过学习向量化编程,我们可知,数据的计算可以利用单指令多数据的方式进行加速,例如上面的例子中的内层循环,下面就使用NEON来试试吧。

3、第2个例子

为了进行向量化加速,首先需要将UV数据分离,将UV数据分离的操作在NEON中很容易进行, 使用vld2交织加载或者储存即可。对于每一行的数据,交织加载的示意图如下。
在这里插入图片描述

交织加载的基本原理是按照间隔挑选数据。交织加载的例子如下所示:

void DownscaleUvNeon()
{
 
    vector<uint8_t> data;  // UVUVUVUVUV...
    for(int i=0;i<32;i++){
        data.push_back(i);
    }
	// 
    uint8_t *src_ptr0 = (uint8_t *)data.data(); 
	
    // load 第一行的数据
    uint8x16x2_t src;
    src = vld2q_u8(src_ptr0);   // 交织读取 16 * 2 的数据,需要两个q寄存器。
    auto a = src_odd.val[0];   // 一行的U数据

    vector<uint8_t> show_data(16);
    vst1q_u8 (show_data.data(),a);   // 将U数据顺序储存到内存中
	
    // 打印
    for(auto n : show_data){
        cout <<  static_cast<int>(n) << endl;  // 0,2,4,6,...
    }   
}
4、第3个例子

对于下UV数据采样来说,在偶数行进行上面的交织加载,再在奇数行上进行同样的操作。奇数行和偶数行相应的数据进行相加再求平均,即可得到最后的结果。代码实现如下:

#include <arm_neon.h>
void DownscaleUvNeon(uint8_t *src, uint8_t *dst, int32_t src_width, int32_t src_stride, int32_t dst_width, int32_t dst_height, int32_t dst_stride)
{
    //用于加载偶数行的源数据,2组每组16个u8类型数据,(16 * 8) * 2 = 128 * 128, 因此需要两个q寄存器。 
    uint8x16x2_t v8_src0;
    //用于加载奇数行的源数据
    uint8x16x2_t v8_src1;
    //目的数据变量,需要一个Q寄存器
    uint8x8x2_t v8_dst;
    //目前只处理16整数倍部分的结果
    int32_t dst_width_align = dst_width & (-16);  //  dst_width & (-16),最大能够整除16的数。
    //向量化剩余的部分需要单独处理
    int32_t remain = dst_width & 15;
    int32_t i = 0;
    //外层高度循环,逐行处理
    for (int32_t j = 0; j < dst_height; j++)
    {
        //偶数行源数据地址
        uint8_t *src_ptr0 = src + src_stride * j * 2;
        //奇数行源数据地址
        uint8_t *src_ptr1 = src_ptr0 + src_stride;
        //目的数据指针
        uint8_t *dst_ptr = dst + dst_stride * j;
        //内层循环,一次16个u8结果输出
        for (i = 0; i < dst_width_align; i += 16)
        {
            //提取数据,进行UV分离
            v8_src0 = vld2q_u8(src_ptr0); 
            src_ptr0 += 32; // 偶数行进入下一个stride
            v8_src1 = vld2q_u8(src_ptr1);
            src_ptr1 += 32; // 奇数行行进入下一个stride
            //水平两个数据相加
            uint16x8_t v16_u_sum0 = vpaddlq_u8(v8_src0.val[0]);
            uint16x8_t v16_v_sum0 = vpaddlq_u8(v8_src0.val[1]);
            uint16x8_t v16_u_sum1 = vpaddlq_u8(v8_src1.val[0]);
            uint16x8_t v16_v_sum1 = vpaddlq_u8(v8_src1.val[1]);
            //上下两个数据相加,之后求均值
            v8_dst.val[0] = vshrn_n_u16(vaddq_u16(v16_u_sum0, v16_u_sum1), 2);
            v8_dst.val[1] = vshrn_n_u16(vaddq_u16(v16_v_sum0, v16_v_sum1), 2);
            //UV通道结果交织存储
            vst2_u8(dst_ptr, v8_dst);
            dst_ptr += 16;
        }
        //process leftovers......
    }
}
5、第4个例子

当图像的宽度不是16的整数倍,需要考虑结尾数据处理,按照链接里面的例子,可以分为以下几种。

1、 padding

​ 也就是将数据补齐到想要的长度,如下图所示,比如我这里需要操作 uint8x8_t的数据,但是我的数据长度只有5,可以将数据的长度填充至8。
在这里插入图片描述

2、Overlap

​ 也就是重复利用其中的某些数据,在不填充其他数据的情况下进行,如下图所示,当需要利用uint8x4_t来对下面的数据进行计算时,可以先将04加载到寄存器上,再将36加载到寄存器上操作。
在这里插入图片描述

常用第二种方法对结尾数据进行处理,那么图像下采样的数据代码可以写成:

#include <arm_neon.h>

void DownscaleUvNeon(uint8_t *src, uint8_t *dst, int32_t src_width, int32_t src_stride, int32_t dst_width, int32_t dst_height, int32_t dst_stride)
{
    uint8x16x2_t v8_src0;
    uint8x16x2_t v8_src1;
    uint8x8x2_t v8_dst;
    int32_t dst_width_align = dst_width & (-16); // 最大能够整除16的数。
    int32_t remain = dst_width & 15;             // 需要剩余处理的数据长度
    int32_t i = 0;

    for (int32_t j = 0; j < dst_height; j++)
    {
        uint8_t *src_ptr0 = src + src_stride * j * 2;
        uint8_t *src_ptr1 = src_ptr0 + src_stride;
        uint8_t *dst_ptr = dst + dst_stride * j;
		
        // 处理完宽度为16的整数倍数据了
        for (i = 0; i < dst_width_align; i += 16)
        {
            v8_src0 = vld2q_u8(src_ptr0);
            src_ptr0 += 32;
            v8_src1 = vld2q_u8(src_ptr1);
            src_ptr1 += 32;
            uint16x8_t v16_u_sum0 = vpaddlq_u8(v8_src0.val[0]);
            uint16x8_t v16_v_sum0 = vpaddlq_u8(v8_src0.val[1]);
            uint16x8_t v16_u_sum1 = vpaddlq_u8(v8_src1.val[0]);
            uint16x8_t v16_v_sum1 = vpaddlq_u8(v8_src1.val[1]);
            v8_dst.val[0] = vshrn_n_u16(vaddq_u16(v16_u_sum0, v16_u_sum1), 2);
            v8_dst.val[1] = vshrn_n_u16(vaddq_u16(v16_v_sum0, v16_v_sum1), 2);
            vst2_u8(dst_ptr, v8_dst);
            dst_ptr += 16;
        }
        // process leftover
        // remain 剩余需要处理的数据长度
        if (remain > 0)
        {
            // 从后往前回退一次向量计算需要的数据长度
            // 有部分数据是之前处理过的,这部分的数据在这里重复计算一次
            src_ptr0 = src + src_stride * (j * 2) + src_width - 32; 
            src_ptr1 = src_ptr0 + src_stride;
            dst_ptr = dst + dst_stride * j + dst_width - 16;
            
            v8_src0 = vld2q_u8(src_ptr0);
            v8_src1 = vld2q_u8(src_ptr1);
            uint16x8_t v16_u_sum0 = vpaddlq_u8(v8_src0.val[0]);
            uint16x8_t v16_v_sum0 = vpaddlq_u8(v8_src0.val[1]);
            uint16x8_t v16_u_sum1 = vpaddlq_u8(v8_src1.val[0]);
            uint16x8_t v16_v_sum1 = vpaddlq_u8(v8_src1.val[1]);
            v8_dst.val[0] = vshrn_n_u16(vaddq_u16(v16_u_sum0, v16_u_sum1), 2);
            v8_dst.val[1] = vshrn_n_u16(vaddq_u16(v16_v_sum0, v16_v_sum1), 2);
            
            vst2_u8(dst_ptr, v8_dst);
        }
    }
}

3、 single

将剩余的元素单独处理,就是将剩余的元素利用NEON的只加载一个元素的功能,不推荐使用,因为这里又可能for循环多次。

4、将剩余的元素当作标量处理

也就是将剩下的元素直接使用c语言编程的方式进行计算。

void DownscaleUvNeonScalar(uint8_t *src, uint8_t *dst, int32_t src_width, int32_t src_stride, int32_t dst_width, int32_t dst_height, int32_t dst_stride)
{
    uint8x16x2_t v8_src0;
    uint8x16x2_t v8_src1;
    uint8x8x2_t v8_dst;
    int32_t dst_width_align = dst_width & (-16);
    int32_t remain = dst_width & 15;
    int32_t i = 0;

    for (int32_t j = 0; j < dst_height; j++)
    {
        uint8_t *src_ptr0 = src + src_stride * j * 2;
        uint8_t *src_ptr1 = src_ptr0 + src_stride;
        uint8_t *dst_ptr = dst + dst_stride * j;

        for (i = 0; i < dst_width_align; i += 16) // 16 items output at one time
        {
            v8_src0 = vld2q_u8(src_ptr0);
            src_ptr0 += 32;
            v8_src1 = vld2q_u8(src_ptr1);
            src_ptr1 += 32;
            uint16x8_t v16_u_sum0 = vpaddlq_u8(v8_src0.val[0]);
            uint16x8_t v16_v_sum0 = vpaddlq_u8(v8_src0.val[1]);
            uint16x8_t v16_u_sum1 = vpaddlq_u8(v8_src1.val[0]);
            uint16x8_t v16_v_sum1 = vpaddlq_u8(v8_src1.val[1]);
            v8_dst.val[0] = vshrn_n_u16(vaddq_u16(v16_u_sum0, v16_u_sum1), 2);
            v8_dst.val[1] = vshrn_n_u16(vaddq_u16(v16_v_sum0, v16_v_sum1), 2);
            vst2_u8(dst_ptr, v8_dst);
            dst_ptr += 16;
        }
        //process leftover
        src_ptr0 = src + src_stride * j * 2;
        src_ptr1 = src_ptr0 + src_stride;
        dst_ptr = dst + dst_stride * j;
        for (int32_t i = dst_width_align; i < dst_width; i += 2)
        {
            dst_ptr[i] = (src_ptr0[i * 2] + src_ptr0[i * 2 + 2] +
                         src_ptr1[i * 2] + src_ptr1[i * 2 + 2]) / 4;

            dst_ptr[i + 1] = (src_ptr0[i * 2 + 1] + src_ptr0[i * 2 + 3] +
                             src_ptr1[i * 2 + 1] + src_ptr1[i * 2 + 3]) / 4;
        }
    }
}
6、总结

本次学习中通过一个下采样的例子学习的NEON编程过程中的优势以及将要面临的问题,主要是剩余数据处理的方式,后面将继续深入学习。

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
完整下载http://vdisk.weibo.com/s/nfF_Q ===================================================================2013-1-1 自动对输出的EXE动画文件进行加密处理,这是一个重要升级,意味着只有自己 的加密狗才可以打开/编辑自己设计的EXE,而别人只能播放EXE而不能打开EXE, 杜绝了骗图、偷图等盗版行为,并切底阻止他人非法提取自己的TOL、图片及动画效果. ===================================================================2012-09-01 1、AVI动画加入MP3音乐功能 2、增加1024X16编译系统 3、NeonPlay 中的程序编辑中块操作中增加直接删除节拍功能 4、NeonPlay 图元移动中增加图元再制次数选项 5、NeonEdit 增加单线芯片的1024X16控制器的输出修改 6、NeonPlay 中修改属性设置过行列数后,再次显示属性时显示行列数都是1的错误 7、NeonPlay 修改图元分割时默认是图元分割 8、NeonPlay 修改花样编辑自动花样V型花样的V型移动和V型翻转的从下到上,从右到左的错误 9、NeonEdit 修改自动花样下V型花样V型移动和V型翻转中从右到左,从下到下在空节 拍生成第一节拍出错的错误 ===================================================================2012-07-03 增加功能 1、NeonPlay支持AVI动画文件输出,播放更通用,同时最大限度保护了设计者的版权 2、修改NeonPlay中动画大高度遮罩时播放演示错误 3、修改NeonEdit编译中单线芯片中2048点控制器线路定义的错误 4、修正编译系统的加密功能 5、升级DMX512的编译系统 6、输出bin文件功能普通用户受限制 ===================================================================2012-05-13 增加功能 1、Neonplay 网格设计区工具菜单下增加Flash/SWF转网格[TOL]功能 2、NeonEdit节拍编辑菜单下增加Flash/SWF转网格[TOL/COT]功能 ===================================================================2012-04-06 1、升级软件保护机制 2、增加编译系统:2048x16 3、修改 NeonPlay 菜单下打开不起作用的错误 4、修正 NeonEdit 修改调色板后面有空白的错误 5、修正工具栏里的节点编辑不起作用---注意:只能编辑贝兹曲线和折 线,每次只能编辑一个图元:一个贝兹曲线或者一个折线 ===================================================================2012-03-10 1、采集器2012增加了保存256等级的cot文件功能,单次最大采集5万帧. 采集器2012需要加密狗才能运行 2、修正NeonEdit另存cot文件的错误 3、NeonPlay 修改了删除遮罩时撤销和返回的错误 4、Neonplay 修改自由变换删除后,再撤销的错误 5、NeonPlay 修改了png遮罩变反 6、点击版本号或者点击NeonPlay的<>菜单可显示最近更新历史 ===================================================================2012-02-28 该版本为重要升级,新版本支持256等级的网格文件和色盘文件, 新256等级网格文件采用.COT扩展名保存,原有的128等级网格 文件.TOL继续保持兼容. 1,增加.COT的256等级网格文件 2,色盘增加256等级颜色 3,色盘增加全选和七种颜色的自定义 4,修正自由变换出错、遮罩删除、图片遮罩反 5,修正与杀毒软件的兼容性 ===================================================================2011-12-08 1,增加导入大图片文件功能,在<>菜单里的最下面 2,增加了 png 图片转成遮罩层 3,平铺后任意变形能否增加弧度调整功能。 如果建筑有弧度需要分解后分跟调整很麻烦!解决方法是 用分割成线段组,不要把它们打断 ===================================================================2011-10-31 增加单线1024X8的编译 ===================================================================2011-10-18 增加DMX512系列控制器的编译 1, 170点X8通道 2, 512点X8通道 3,1024点X8通道 ===================================================================2011-07-22 1,解决输出的EXE被当病毒杀 2,升级单线芯片的编译系统 ===================================================================2011-07-07 1.动画像素提升到:1024*768 2.可以支持输出静态图片功能,客户可以打印静态的效果。 3.解决问题: 输出动画后,画面和制作界面大小不一致,自动裁切下面的,动画图像很靠下。 4.解决问题: 画图界面按空格画图的时候拖动画面的时候是反方向,和ps也是相反的。 5.升级了 PLAY2006 播放器 升级到 PLAY2011
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Rex久居

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值