首先,Qt5.X移植到海思平台以及linuxfb插件的修改可以参考https://www.cnblogs.com/chay/p/10431403.html一文,本文介绍的优化过程与之相似,只是结合了具体的项目进行了分析和改进,本文思路应广泛适用于海思平台。
本文包含了大量的个人见解,如果有理解错误或者片面的地方,还请大家不吝惜时间和精力给予指正。
-
概述
Qt的源码中通过 Q<pluginType>Factory、Q<pluginType>Plugin 和 Q<pluginType> 这三个类实现了Qt的插件加载机制,
这个机制可用于加载特定种类的插件。Arm上Qt绘图一般会使用linuxfb来进行画图,由于Hi3519V101/Hi3516AV200的cpu主频较低和Qt绘图效率的问题,在需要绘制全屏UI或者UI刷新率较高时,会有明显地切屏和撕裂现象,在四核的Hi3559AV100平台上,切屏现象是非常不明显的。
-
优化点
将linux frambuffer替换成Hisilicon Framebuffer,并使用TDE进行快速位图搬移/缩放
从qt源码可以看出,linuxfb只map出了一片绘图缓存区并没有特别的buffer管理机制,这意味着你的画图过程将会全程展现在使用者面前,在高性能的cpu下这个过程可能并不明显,但是在Hi3519V101/Hi3516AV200平台下就会出现很明显的切屏(理论上大核主频也有1G了,这里我不是很理解,难道是Qt本身的绘图机制效率低了?)。
uchar *data = (unsigned char *)mmap(0, mMmap.size, PROT_READ | PROT_WRITE, MAP_SHARED, mFbFd, 0);
if ((long)data == -1) {
qErrnoWarning(errno, "Failed to mmap framebuffer");
return false;
}
mMmap.offset = geometry.y() * mBytesPerLine + geometry.x() * mDepth / 8;
mMmap.data = data + mMmap.offset;
QFbScreen::initializeCompositor();
mFbScreenImage = QImage(mMmap.data, geometry.width(), geometry.height(), mBytesPerLine, mFormat);
下面贴下使用hifb的特性,摘自海思的《HIFB开发指南》
HiFB 支持以下的 Linux Framebuffer 标准功能:
将物理显存映射(或解除映射)到虚拟内存空间。
像操作普通文件一样操作物理显存。
设置硬件显示分辨率和象素格式,每个叠加图形层的支持的最大分辨率和象素格
式可以通过支持能力接口获取。
从物理显存的任何位置进行读、写、显示等操作。
在叠加图形层支持索引格式的情况下,支持设置和获取 256 色的调色板。
HiFB 增加以下的扩展功能:
设置和获取叠加图形层的 Alpha 值。
设置和获取叠加图形层的 colorkey 值。
设置当前叠加图形层的起始位置(相对于屏幕原点的偏移)。
设置和获取当前叠加图形层的显示状态(显示/隐藏)。
通过模块加载参数配置 HiFB 的物理显存大小和管理叠加图形层的数目。
设置和获取抗闪烁功能的状态。
设置和获取预乘模式。
设置和获取压缩模式的状态。
设置和获取内存检测的状态。
设置/获取图形层刷新类型(0 buffer、1 buffer 与 2 buffer)。
支持软鼠标的一系列操作。
下面详细介绍优化的点
- 使用带cache的mmz内存提高buffer的存取效率。
//给Qt绘制的位图buffer(虚拟地址)申请一块物理地址 图像格式ARGB8888 QImageData * QtPic = mScreenImage->data_ptr(); free(QtPic->data); //QFbScreen::setComposite(); if (HI_FAILURE == HI_MPI_SYS_MmzAlloc_Cached(&g_Phyaddr, ((void**)&(QtPic->data)), NULL, NULL, width * height * 4)) { qWarning("allocate memory (maxW*maxH*2 bytes) failed\n"); close(mFbFd); return false; } //给hifb的画布buffer申请一块物理内存 图像格式ARGB1555 if (HI_FAILURE == HI_MPI_SYS_MmzAlloc_Cached(&g_CanvasAddr, ((void**)&pBuf), NULL, NULL, width * height * 2)) { qWarning("allocate memory (maxW*maxH*2 bytes) failed\n"); close(mFbFd); return false; }
- 使用hifb提供的图形层刷新模式。
HiFB 为上层用户提供了一套完整的刷新方案,称为 FB 的扩展模式。在对系统性能、内存、图形显示效果各方面综合衡量的基础上,可根据需要选择合适的刷新方案。目前提供的刷新类型有:
0 buffer(即 HIFB_LAYER_BUF_NONE)
上层用户的绘制 buffer 即为显示 buffer。该刷新类型可以节省内存消耗,速度也最快,但是用户会看到图形的绘制过程。
1 buffer(即 HIFB_LAYER_BUF_ONE)
显示 buffer 由 HIFB 提供,因此需要一定的内存。该刷新类型是对显示效果和内存需求的一种折中考虑。但是会有锯齿。
2 buffer(即HIFB_LAYER_BUF_DOUBLE和 HIFB_LAYER_BUF_DOUBLE_IMMEDIATE)
显示 buffer 由 HIFB 提供。和前面的刷新类型相比,其要求内存最多,但图形显示效果最好stLayerinfo.BufMode = HIFB_LAYER_BUF_DOUBLE_IMMEDIATE; stLayerinfo.u32Mask = HIFB_LAYERMASK_BUFMODE; stLayerinfo.u32CanvasWidth = width; stLayerinfo.u32CanvasHeight = height; stLayerinfo.u32DisplayWidth = width; stLayerinfo.u32DisplayHeight = height; stLayerinfo.s32XPos = 0; stLayerinfo.s32YPos = 0; stLayerinfo.u32Mask |= HIFB_LAYERMASK_DISPSIZE | HIFB_LAYERMASK_POS|HIFB_LAYERMASK_CANVASSIZE; if (ioctl(mFbFd, FBIOPUT_LAYER_INFO, &stLayerinfo) < 0) { printf("FBIOPUT_LAYER_INFO failed!\n"); close(mFbFd); return HI_NULL; } /*enable fb to play*/ bShow = HI_TRUE; if (ioctl(mFbFd, FBIOPUT_SHOW_HIFB, &bShow) < 0) { printf("FBIOPUT_SHOW_HIFB failed!\n"); close(mFbFd); return HI_NULL; }
使用TDE进行快速位图缩放(色彩空间转换)来节省带宽和加速。Qt的QImage类支持的图像格式包括QImage::Format_RGB32(ARGB8888),QImage::Format_RGB888,QImage::Format_RGB16(RGB565),QImage::Format_RGB555,QImage::Format_RGB444。而hifb支持的图形层格式有ARGB8888,ARGB1555,ARGB4444,由于项目需求,绘制的UI一般都带有透明度分量,那么在没有进过特殊处理的情况下,我们只能使用ARGB8888的方式来进行画图。那么我们的显存就需要申请显示分辩率x4的大小,在相同的图形下,也意味着绘图效率的倍增。而TDE(Two Dimensional Engine)利用硬件为 OSD(On Screen Display)和 GUI(Graphics User Interface)提供快速的图形绘制功能。其接口可以直接操作物理地址,将Qt的绘制buffer直接快速搬到hifb的画布上
QRegion QHiFbScreen::doRedraw()
{
QRegion touched = QFbScreen::doRedraw();
TDE2_RECT_S stSrcRect, stDstRect;
TDE2_SURFACE_S stSrc, stDst;
HI_S32 s32Ret;
HI_U32 u32HideScreenPhy = 0;
HI_U32 u32showScreenPhy = 0;
HI_U32 u32FixScreenStride = 0;
TDE_HANDLE s32Handle;
HIFB_BUFFER_S stCanvasBuf;
static HIFB_ROTATE_MODE_E prev_rotate = HIFB_ROTATE_NONE;
struct timeval start;
struct timeval end;
if (touched.isEmpty())
return touched;
QImageData * QtPic = mScreenImage->data_ptr();
fb_var_screeninfo vinfo;
fb_fix_screeninfo fix;
if (ioctl(mFbFd, FBIOGET_FSCREENINFO, &fix) != 0) {
qErrnoWarning(errno, "Error reading fixed information");
}
if (ioctl(mFbFd, FBIOGET_VSCREENINFO, &vinfo)) {
qErrnoWarning(errno, "Error reading variable information");
}
stCanvasBuf.stCanvas.u32PhyAddr = g_CanvasAddr;
stCanvasBuf.stCanvas.u32Height = vinfo.yres;
stCanvasBuf.stCanvas.u32Width = vinfo.xres;
stCanvasBuf.stCanvas.u32Pitch = vinfo.xres * 2;
stCanvasBuf.stCanvas.enFmt = HIFB_FMT_ARGB1555;
const QVector<QRect> rects = touched.rects();
int x,y,w,h;
rects[0].getRect(&x,&y,&w,&h);
HIFB_ROTATE_MODE_E rotate;
/* 0. open tde */
stSrcRect.s32Xpos = x;
stSrcRect.s32Ypos = y;
stSrcRect.u32Height = h;
stSrcRect.u32Width = w;
stDstRect.s32Xpos = x;
stDstRect.s32Ypos = y;
stDstRect.u32Height = h;
stDstRect.u32Width = w;
stDst.enColorFmt = TDE2_COLOR_FMT_ARGB1555;
stDst.u32Stride = vinfo.xres*2;
stSrc.enColorFmt = TDE2_COLOR_FMT_ARGB8888;
stSrc.u32Stride = vinfo.xres*4;
stDst.u32Width = vinfo.xres;
stDst.u32Height = vinfo.yres;
stDst.u32PhyAddr = stCanvasBuf.stCanvas.u32PhyAddr;
stSrc.u32Width = vinfo.xres;
stSrc.u32Height = vinfo.yres;
stSrc.u32PhyAddr = g_Phyaddr;
stSrc.bAlphaExt1555 = HI_TRUE;
stSrc.bAlphaMax255 = HI_TRUE;
stSrc.u8Alpha0 = 0XFF;
stSrc.u8Alpha1 = 0XFF;
s32Ret = ioctl(mFbFd, FBIOGET_VBLANK_HIFB);
s32Handle = HI_TDE2_BeginJob();
HI_MPI_SYS_MmzFlushCache(g_CanvasAddr, (void*)pBuf,1920*1080*2);
if (HI_ERR_TDE_INVALID_HANDLE == s32Handle)
{
printf("start job failed!\n");
HI_MPI_SYS_MmzFree(g_Phyaddr, QtPic->data);
g_Phyaddr = 0;
HI_MPI_SYS_MmzFree(g_CanvasAddr, pBuf);
g_CanvasAddr = 0;
close(mFbFd);
}
//开启TDE快速位图缩放任务,支持位图从src到dst进行缩放,图像格式转换
s32Ret = HI_TDE2_QuickResize(s32Handle, &stSrc, &stSrcRect, &stDst, &stDstRect);
if (s32Ret < 0)
{
printf("HI_TDE2_QuickCopy:%d failed,ret=0x%x!\n", __LINE__, s32Ret);
HI_TDE2_CancelJob(s32Handle);
HI_MPI_SYS_MmzFree(g_Phyaddr, QtPic->data);
g_Phyaddr = 0;
HI_MPI_SYS_MmzFree(g_CanvasAddr, pBuf);
g_CanvasAddr = 0;
close(mFbFd);
}
/* 3. submit job */
s32Ret = HI_TDE2_EndJob(s32Handle, HI_FALSE, HI_FALSE, -1);
if (s32Ret < 0)
{
printf("Line:%d,HI_TDE2_EndJob failed,ret=0x%x!\n", __LINE__, s32Ret);
HI_TDE2_CancelJob(s32Handle);
HI_MPI_SYS_MmzFree(g_Phyaddr, QtPic->data);
g_Phyaddr = 0;
HI_MPI_SYS_MmzFree(g_CanvasAddr, pBuf);
g_CanvasAddr = 0;
close(mFbFd);
}
//设置当前画布的大小
stCanvasBuf.UpdateRect.x = x;
stCanvasBuf.UpdateRect.y = y;
stCanvasBuf.UpdateRect.w = w;
stCanvasBuf.UpdateRect.h = h;
HI_MPI_SYS_MmzFlushCache(g_CanvasAddr, (void*)pBuf,1920*1080*2);
s32Ret = ioctl(mFbFd, FBIO_REFRESH, &stCanvasBuf);
return touched;
}
-
不足
结合我自己的实践,HIFB_LAYER_BUF_DOUBLE_IMMEDIATE模式下式绝对不会出现切屏的现象,带相对的牺牲的了画图速率,如果对刷新率和内存没有什么要求,这种模式是最保险的。其他三种模式的速度差不多,但是当图形刷新率特别高的时候还是会出现没画完就显示的情况,估计还是cpu本身性能不够,导致图形渲染速度慢,如果实时性要求高的UI建议还是用VGS模块去绘制。