关于OTA升级动画分屏策略的探索
本文为探索在recovery模式进行UI分屏的研究
OTA升级过程中,流程是先将UI元素通过LoadBitmap的方式,读到对应的surface中,将各个surface画到一个绘制buffer里面,因为这里是需要做双眼分屏方案,故而buffer为半个屏幕大小。画好绘制buffer之后将绘制buffer以拷贝的方式,刷到显示buffer上去。
初期的方案是在graphics_fbdev.cpp中的page_flip中加入合适的刷新方式,从而将绘制好的gr_draw通过memcpy的方式刷到显示buffer framebuffer[displayed]上去。
但是这种方式存在一个问题,也就是本文主要解决的一个bug,这个问题就是,gr_draw是 Horizontal的方式进行读取的,而目标显示是横屏的(图形的方向是“^” ,“^”绘制进去就是“<”,因此需要将图像先右旋90度“>”.这样通过刷新就可以正常显示)这也是memcpy函数采用了将gr_draw的第j行第i列的像素copy到gr_framebuffer[displayed]的第i行的第w2-j-(w2-gr_draw->height)/2个像素了,其中(w2-gr_draw->height)是裁去了屏幕的多余的部分,因为屏幕是w2160h3840的横向显示屏,半屏大小是21601920,这里绘制buffer取的是1920*1920的正方形区域,实际上旋转对是否是正方形没有要求,满足Aij=Bj(n-i-1)即可。
这样处理存在一个问题,每一次进行flip切换显示buffer时候,因为这里是通过双循环的方式去做memcpy,效率极其低下,没切换一帧耗时200ms。导致绘制的UI极其卡顿。
+LOCAL_CFLAGS += -DDUALEYE_SQUARE
+#ifdef DUALEYE_SQUARE
+ gr_draw = (GRSurface*) malloc(sizeof(GRSurface));
+ memcpy(gr_draw, gr_framebuffer, sizeof(GRSurface));
+
+ gr_draw->height=min(gr_draw->height/2,gr_draw->width);
+ gr_draw->width=gr_draw->height;
+ gr_draw->row_bytes=(gr_draw->row_bytes*gr_draw->width)/gr_framebuffer[0].width;
+ gr_draw->data = (unsigned char*) malloc(gr_draw->height * gr_draw->row_bytes);
+ if (!gr_draw->data) {
+ perror("failed to allocate in-memory surface");
+ return NULL;
+ }
+
+#else
gr_draw = gr_framebuffer+1;
+#endif
+#ifdef DUALEYE_SQUARE
+ int i,j;
+ int w2,h2;
+ w2=gr_framebuffer[0].width;
+ for(i=0;i<gr_draw->width;i++)
+ for(j=0;j<gr_draw->height;j++)
+ {
+ int x1 = w2-j-(w2-gr_draw->height)/2;
+ int y1 = i;
+ int x2 = x1;
+ int y2=i+gr_framebuffer[0].height/2;
+ //rotate 90degree for left eye
+ memcpy(gr_framebuffer[displayed_buffer].data+y1*gr_framebuffer[0].row_bytes+x1*gr_framebuffer[0].pixel_bytes,
+ gr_draw->data+j*gr_draw->row_bytes+i*gr_framebuffer[0].pixel_bytes,
+ gr_framebuffer[0].pixel_bytes);
+
+ //rotate 90degree for right eye
+ memcpy(gr_framebuffer[displayed_buffer].data+y2*gr_framebuffer[0].row_bytes+x2*gr_framebuffer[0].pixel_bytes,
+ gr_draw->data+j*gr_draw->row_bytes+i*gr_framebuffer[0].pixel_bytes,
+ gr_framebuffer[0].pixel_bytes);
+ }
+#else
gr_draw = gr_framebuffer + displayed_buffer;
+#endif
static void fbdev_exit(minui_backend* backend __unused) {
free(gr_draw->data);
free(gr_draw);
}
+
+#ifdef DUALEYE_SQUARE
+ if (gr_draw) {
+ free(gr_draw->data);
+ free(gr_draw);
+ }
+#endif
gr_draw = NULL;
}
为了解决这个问题,采取很多的尝试,首先尝试了去改写memcpy函数,通过采取按int型读取的方式,相当于四字节一次的拷贝,因为原有的数据结构是如下结构,结构体的数据是char型指针,每次偏移的是一个字符的地址,虽然设定的步长是gr_framebuffer[0].pixel_bytes,该值为4,相当于嵌套三次的循环,如果修改memcpy函数,让其按int型指针,一次取4个字节,可能能提升4倍的速度。这里基于的知识是,按字节memcpy和按int型memcpy的速度不一样,后面证实,cpy方式确实会影响速率。
struct GRSurface {
int width;
int height;
int row_bytes;
int pixel_bytes;
unsigned char* data;
};
memcpy函数修改,但是通过实际测试,效果并没有多大提升。
void *memcpy(void *dest, const void *src, size_t n)
{
int *d = (int *) dest
const int *s = (const int *) src;
while (n--)
*d++ = *s++;
value = 1;
return dest;
}
然后同时又根据刷新效率的考量,修改了如下方式,让memcpy按行刷新,代码如下。这里刷新效率大为提升,但是没有做旋转的工作,导致这里画面显示方向为“<”。因为采用的是一层循环,算法复杂度为f(n)。这里考虑了一下,如果想要将刷新和旋转一起做,必须至少是按int型四像素一起copy,或者是按像素拷贝,效率目前来看都是无法提升的。所以,关注点,改为放到了绘制阶段,将图绘制到gr_draw上面的时候就直接画成">"这个方向。
int y1 = gr_framebuffer[0].height/2;
for(int i=0;i<gr_draw->width;i++){
//rotate 90degree for left eye
memcpy(gr_framebuffer[displayed_buffer].data+i*gr_framebuffer[0].row_bytes,
gr_draw->data+i*gr_draw->row_bytes,
gr_draw->row_bytes);
//rotate 90degree for right eye
memcpy(gr_framebuffer[displayed_buffer].data+(i+y1)*gr_framebuffer[0].row_bytes,
gr_draw->data+i*gr_draw->row_bytes,
gr_draw->row_bytes);
}
这里就需要将所有元素变成竖向的资源,这里只用在图片查看器里面进行一下旋转然后保存即可。然后调整刷新方式,让绘制函数按照新的刷新方式,新的目标位置进行刷新。修改内容如下。
int ScreenRecoveryUI::GetAnimationBaseline() {
- return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) -
+ return GetTextBaseline() - PixelsFromDp(120) -
gr_get_height(loopFrames[0]);
}
int ScreenRecoveryUI::GetTextBaseline() {
- return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) -
+ return GetProgressBaseline() - PixelsFromDp(32) -
gr_get_height(installing_text);
}
int ScreenRecoveryUI::GetProgressBaseline() {
- return gr_fb_height() - PixelsFromDp(kLayouts[layout_][PROGRESS]) -
+ return gr_fb_width() - PixelsFromDp(833) -
gr_get_height(progressBarFill);
}
@@ -163,8 +163,8 @@
}
GRSurface* text_surface = GetCurrentText();
- int text_x = (gr_fb_width() - gr_get_width(text_surface)) / 2;
- int text_y = GetTextBaseline();
+ int text_x = 900;
+ int text_y = (gr_fb_width() - gr_get_height(text_surface)) / 2;
gr_color(255, 255, 255, 255);
gr_texticon(text_x, text_y, text_surface);
}
@@ -178,25 +178,24 @@
GRSurface* frame = GetCurrentFrame();
int frame_width = gr_get_width(frame);
int frame_height = gr_get_height(frame);
- int frame_x = (gr_fb_width() - frame_width) / 2;
- int frame_y = GetAnimationBaseline();
- gr_blit(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
+ int frame_x = gr_fb_width() - gr_get_width(frame)-299;
+ int frame_y = (gr_fb_width()-gr_get_width(frame))/2;
+ gr_blit(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
}
if (progressBarType != EMPTY) {
int width = gr_get_width(progressBarEmpty);
int height = gr_get_height(progressBarEmpty);
- int progress_x = (gr_fb_width() - width)/2;
- int progress_y = GetProgressBaseline();
-
+ int progress_x = (gr_fb_width() - 368)/2;
+ int progress_y = (gr_fb_width()-gr_get_height(progressBarEmpty))/2;
// Erase behind the progress bar (in case this was a progress-only update)
gr_color(0, 0, 0, 255);
gr_fill(progress_x, progress_y, width, height);
if (progressBarType == DETERMINATE) {
float p = progressScopeStart + progress * progressScopeSize;
- int pos = (int) (p * width);
+ int pos = (int) (p * height);
if (rtl_locale) {
// Fill the progress bar from right to left.
@@ -210,11 +209,10 @@
} else {
// Fill the progress bar from left to right.
if (pos > 0) {
- gr_blit(progressBarFill, 0, 0, pos, height, progress_x, progress_y);
+ gr_blit(progressBarFill, 0, 0, width, pos, progress_x, progress_y);
}
- if (pos < width-1) {
- gr_blit(progressBarEmpty, pos, 0, width-pos, height,
- progress_x+pos, progress_y);
+ if (pos < height-1) {
+ gr_blit(progressBarEmpty, 0, pos, width, height-pos,progress_x, progress_y+pos);
}
}
}
唯一不同是升级文字,这里文字采取的方式是,通过读取地点local=Zn_ch,然后去读install_text.PNG资源,这里只分析一下如下代码,通过新建一个row型指针,因为install_text有很多行的各国语言的资源。逐行通过png_read_row去从图片中取数据放到row所指向的区域。通过row[1]的左移8位与row[0]的按位或得到文字的宽度,同样的方式处理2.3得到文字的高度,row[5]里面存放的是属于哪国的语言。如果超出范围或者语言和地区匹配则进入循环,这里新建了一个surface用来存放读取出来的图片。这里由于图片比较小,采用双循环的方式,对图片进行左旋90度。将旋转后的surface送到输出surface指针中。
row = reinterpret_cast<unsigned char*>(malloc(width));
for (y = 0; y < height; ++y) {
png_read_row(png_ptr, row, NULL);
int w = (row[1] << 8) | row[0];
int h = (row[3] << 8) | row[2];
int len = row[4];
char* loc = (char*)row+5;
if (y+1+h >= height || matches_locale(loc, locale)) {
- printf(" %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y);
-
- surface = malloc_surface(w*h);
+ printf("match success name=%20s: loc=%20s ;locale=%s ;(w = %d x h = %d @ y =%d)\n", name, loc, locale,w, h, y);
+ surface = malloc_surface(h*w);
if (surface == NULL) {
result = -8;
goto exit;
}
- surface->width = w;
- surface->height = h;
+ surface->width = h;
+ surface->height = w;
surface->row_bytes = w;
surface->pixel_bytes = 1;
- int i;
- for (i = 0; i < h; ++i, ++y) {
+ for (int i = 0; i < h; ++i, ++y) {
png_read_row(png_ptr, row, NULL);
- memcpy(surface->data + i*w, row, w);
+ for(int j =0; j< w; ++j){
+ memcpy(surface->data+j*surface->row_bytes+(h-i)*surface->pixel_bytes,
+ &row[j],
+ 1);
+ }
}
-
*pSurface = reinterpret_cast<GRSurface*>(surface);
break;
} else {