撕裂现象
上一篇介绍了单buffer用例,在实际应用至少都是双buffer。2012年android提出了黄油计划采用了3 buffer,使得android系统性能在流畅度方面得到了很大提升。更有国内某手机在播放高帧率视频时更是采用了4 buffer方案。
单buffer最明显的问题就是tear effect(撕裂现象)。这里通过代码演示下撕裂现象,进而引出双buffer用例。
本文代码是在modeset-single-buffer基础上进行了修改。
单buffer撕裂产生 的原因:
单buffer数据在前台显示时,由于随后的画面数据需要再次使用该buffer时由于耗时没有及时填充完,而导致下一帧显示时有部分数据是前一帧的。
演示思路:
有2帧画面,每隔1s会显示一帧画面。第一帧灰色,第二帧白色。第二帧耗时2s,而在第2s的时候只能填充第二帧的一半数据,导致有一半是灰色,一半是白色。
根据modeset_create_fb函数修改,新增代码:
static int vsync_frame(int fd, struct buffer_object *bo, int frame)
{
struct drm_mode_create_dumb create = {};
struct drm_mode_map_dumb map = {};
create.width = bo->width;
create.height = bo->height;
create.bpp = 32;
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
bo->pitch = create.pitch;
bo->size = create.size;
bo->handle = create.handle;
drmModeAddFB(fd, bo->width, bo->height, 24, 32, bo->pitch,
bo->handle, &bo->fb_id);
map.handle = create.handle;
drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
bo->vaddr = mmap(0, create.size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, map.offset);
//帧率填充不同数据
if(frame == 1) {
memset(bo->vaddr, 0x80, bo->size);
} else if (frame == 2) {
memset(bo->vaddr, 0xcc, bo->size/2);
memset(bo->vaddr + bo->size/2, 0x80, bo->size/2);
} else if (frame == 3) {
memset(bo->vaddr, 0xcc, bo->size);
}
return 0;
}
//main函数修改:
int main(int argc, char **argv)
{
...
buf.height = conn->modes[0].vdisplay;
// modeset_create_fb(fd, &buf);
int frame = 1;
while (frame < 4) {
vsync_frame(fd, &buf, frame);
drmModeSetCrtc(fd, crtc_id, buf.fb_id,
0, 0, &conn_id, 1, &conn->modes[0]);
frame ++;
sleep(1);
}
getchar();
...
}
实现效果如下:
双buffer
由于单buffer存在撕裂现象。所以将新帧画面绘制到framebuffer时,需要绘制到一个新buffer,而不是front-buffer(即正在使用的buffer),避免这种现象发生的技术称为double-buffering。双bufferd:front-buffer用于前台显示,back-buffer用于后台绘制。当一帧渲染完成时,进行交换2个buffer(即swapbuffer)。交换并不是指复制数据,而是只交换buffer指针。在绘制到front-buffer之前,我们需要找到back-buffer。每个buffer由宽度、高度、步幅、大小、句柄、地图和 fb-id 组成。
参考何小龙double代码进行修改:
static int vsync_frame(int fd, struct buffer_object *bo, int color)
{
...
memset(bo->vaddr, color, bo->size);
return 0;
}
int main(int argc, char **argv)
{
...
bufs[0].width = conn->modes[0].hdisplay;
bufs[0].height = conn->modes[0].vdisplay;
bufs[1].width = conn->modes[0].hdisplay;
bufs[1].height = conn->modes[0].vdisplay;
vsync_frame(fd, &bufs[0], 0x80);
drmModeSetCrtc(fd, crtc_id, bufs[0].fb_id,
0, 0, &conn_id, 1, &conn->modes[0]);
sleep(1);
vsync_frame(fd, &bufs[1], 0xcc);
drmModeSetCrtc(fd, crtc_id, bufs[1].fb_id,
0, 0, &conn_id, 1, &conn->modes[0]);
sleep(1);
// modeset_create_fb(fd, &buf);
getchar();
modeset_destroy_fb(fd, &bufs[0]);
modeset_destroy_fb(fd, &bufs[1]);
...
}