openharmony的默认代码使用通过计算方式获取的vblank值,这个值是20ms,但是rk3588默认支持drmVBlank,所以可以直接使用libdrm的drmWaitVBlank接口等待每一个vblank的同步,从而提高ui的响应性能,如下是处理方式
通常的,我们知道一个显示屏有display timing参数,对于dsi屏幕,举例如下:
disp_timings0: display-timings { native-mode = <&dsi_timing0>; dsi_timing0: timing0 { clock-frequency = <72600000>; hactive = <800>; vactive = <1280>; hsync-len = <14>; hback-porch = <26>; hfront-porch = <32>; vsync-len = <8>; vback-porch = <20>; vfront-porch = <80>; hsync-active = <0>; vsync-active = <0>; de-active = <0>; pixelclk-active = <0>; }; };
这里我们可以计算出来屏幕的刷新率,如下公式
刷新率 = 时钟/((有效宽度+HSYNC宽+HBP+HFP)(有效高度+VSYNC宽+VBP+VFP))
这里的刷新率,即屏幕的刷新率,通常是60hz。也就是系统每刷新一帧图像需要的实现的倒数,即vsync值。
而对于linux而言,drm框架抽象了vsync的概念,将其称之为vblank,也就意味着,在linux平台上,可以通过drm的接口获取到当前真实硬件的每帧刷新时间。从而可以帮助系统软件gpu渲染时更好的对接到硬件屏幕。
在瑞芯微平台,我们知道瑞芯微存在一个vop驱动,路径为drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
,这个vop驱动将显示图像放在vp buffer内,从而直接送往硬件,vop存在于drm框架的底层实现上。
关于vop2的中断函数如下:
static irqreturn_t vop2_isr(int irq, void *data) { ...... if (active_irqs & FS_FIELD_INTR) { rockchip_drm_dbg(vop2->dev, VOP_DEBUG_VSYNC, "vsync_vp%d\n", vp->id); vop2_wb_handler(vp); if (likely(!vp->skip_vsync) || (vp->layer_sel_update == false)) { drm_crtc_handle_vblank(crtc); vop2_handle_vblank(vop2, crtc); } active_irqs &= ~FS_FIELD_INTR; ret = IRQ_HANDLED; } }
关于vop2触发vblank的函数如下:
static void vop2_handle_vblank(struct vop2 *vop2, struct drm_crtc *crtc) { struct drm_device *drm = vop2->drm_dev; struct vop2_video_port *vp = to_vop2_video_port(crtc); unsigned long flags; spin_lock_irqsave(&drm->event_lock, flags); if (vp->event) { drm_crtc_send_vblank_event(crtc, vp->event); drm_crtc_vblank_put(crtc); vp->event = NULL; } spin_unlock_irqrestore(&drm->event_lock, flags); if (test_and_clear_bit(VOP_PENDING_FB_UNREF, &vp->pending)) drm_flip_work_commit(&vp->fb_unref_work, system_unbound_wq); }
根据drm_crtc_send_vblank_event drm_crtc_vblank_put 我们可以知道,他发送了vblank事件,这样如果上层应用程序调用到内核的drm_wait_vblank_ioctl就可以获取到每个vblank的时间。
所以,真实的vsync,应该是vop2驱动在发送vop2_isr的那一刻。
在代码device/soc/rockchip/rk3588/hardware/display/src/display_device/drm_display.cpp
中,存在函数DrmDisplay::WaitForVBlank,此函数会被hdi层调用,其目前实现为
int32_t DrmDisplay::WaitForVBlank(uint64_t *ns) { DISPLAY_CHK_RETURN((ns == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("in ns is nullptr")); usleep(1000*20); *ns = 1000*1000*20; return DISPLAY_SUCCESS; }
在代码device/soc/rockchip/rk3588/hardware/display/src/display_device/drm_vsync_worker.cpp
中,存在函数DrmVsyncWorker::WaitNextVBlank,如下
uint64_t DrmVsyncWorker::WaitNextVBlank(unsigned int &sq) { constexpr uint64_t SEC_TO_NSEC = 1000 * 1000 * 1000; struct timespec current; usleep(1000*20); sq = 1; clock_gettime(CLOCK_MONOTONIC, ¤t); return (uint64_t)(current.tv_sec * SEC_TO_NSEC + current.tv_nsec); }
WaitNextVBlank作为thread定期等待vsync信号来触发Vsync的callback,如下
void DrmVsyncWorker::WorkThread() { DISPLAY_LOGD(); unsigned int seq = 0; while (WaitSignalAndCheckRuning()) { // wait the vblank uint64_t time = WaitNextVBlank(seq); if (mCallBack != nullptr) { mCallBack->Vsync(seq, time); } else { DISPLAY_LOGE("the callbac is nullptr"); } } }
故可以发现,Openharmony默认使用计时等待的方式来等待vsync,而rk3588默认实际上可以支持vblank,则可以修改vblank,调用drm接口,使得显示支持vblank。
根据上述代码分析,我们可以将openharmony默认的vblank修改为硬件等待的方式,从而使得系统更加流畅。主要修改代码如下
关于drm_display.cpp
,如下
int32_t DrmDisplay::WaitForVBlank(uint64_t *ns) { int ret; constexpr uint64_t nPerS = 1000000000; constexpr uint64_t nPerUS = 1000; drmVBlank vbl = { .request.type = DRM_VBLANK_RELATIVE, .request.sequence = 0, .request.signal = 0, }; DISPLAY_CHK_RETURN((ns == nullptr), DISPLAY_NULL_PTR, DISPLAY_LOGE("in ns is nullptr")); ret = drmWaitVBlank(mDrmDevice->GetDrmFd(), &vbl); DISPLAY_CHK_RETURN((ret != 0), DISPLAY_FAILURE, DISPLAY_LOGE("wait vblank failed errno %{public}d", errno)); *ns = static_cast<uint64_t>(vbl.reply.tval_sec * nPerS + vbl.reply.tval_usec * nPerUS); return DISPLAY_SUCCESS; }
关于drm_vsync_worker.cpp
,如下
uint64_t DrmVsyncWorker::WaitNextVBlank(unsigned int &sq) { constexpr uint64_t SEC_TO_NSEC = 1000 * 1000 * 1000; constexpr uint64_t USEC_TO_NSEC = 1000; drmVBlank vblank = { .request = drmVBlankReq { .type = DRM_VBLANK_RELATIVE, .sequence = 1, .signal = 0, } }; /* The drmWaitVBlank need set the crtc pipe when there are multi crtcs in the system. */ if (mCallBack->GetPipe() == 1) vblank.request.type = drmVBlankSeqType((int)(vblank.request.type) | (int)DRM_VBLANK_SECONDARY); int ret = drmWaitVBlank(mDrmFd, &vblank); DISPLAY_CHK_RETURN((ret < 0), 0, DISPLAY_LOGE("wait vblank failed ret : %{public}d errno %{public}d", ret, errno)); sq = vblank.reply.sequence; return (uint64_t)(vblank.reply.tval_sec * SEC_TO_NSEC + vblank.reply.tval_usec * USEC_TO_NSEC); }
完整补丁如 0001-rk3588-drm-vblank-wait.patch
完成代码修改后,需要编译验证,如下是编译脚本
prebuilts/build-tools/linux-x86/bin/ninja -w dupbuild=warn -C out/rk3588/ display_composer_vendor mount /root/tf/rk3588/out/rk3588/packages/phone/images/vendor.img /tmp/temp/ md5sum /tmp/temp/lib64/libdisplay_composer_vendor.z.so cp /root/tf/rk3588/out/rk3588/hdf/rockchip_products/libdisplay_composer_vendor.z.so /tmp/temp/lib64/libdisplay_composer_vendor.z.so md5sum /tmp/temp/lib64/libdisplay_composer_vendor.z.so umount /tmp/temp/
编译后,更新烧录vendor.img即可