SurfaceFlinger需要hw vsync来对sw vsync进行校准,来防止误差变大。
在hwc中有个VSyncWorker的实现来做这个事情。
来看下VSyncWorker::Routine(),代码有删减。
void VSyncWorker::Routine() {
int display = display_;
std::shared_ptr<VsyncCallback> callback(callback_);
DrmCrtc *crtc = drm_->GetCrtcForDisplay(display);
uint32_t high_crtc = (crtc->pipe() << DRM_VBLANK_HIGH_CRTC_SHIFT);
drmVBlank vblank;
memset(&vblank, 0, sizeof(vblank));
vblank.request.type = (drmVBlankSeqType)(
DRM_VBLANK_RELATIVE | (high_crtc & DRM_VBLANK_HIGH_CRTC_MASK));
vblank.request.sequence = 1;
int64_t timestamp;
ret = drmWaitVBlank(drm_->fd(), &vblank);
timestamp = (int64_t)vblank.reply.tval_sec * kOneSecondNs +
(int64_t)vblank.reply.tval_usec * 1000;
if (callback)
callback->Callback(display, timestamp);
if (enabled_ && vsync_callback_hook_ && vsync_callback_data_)
vsync_callback_hook_(vsync_callback_data_, display, timestamp);
last_timestamp_ = timestamp;
}
里面最重要的操作drmWaitVBlank;从drm中获取drmVBlank vblank信息。
获取成功后更新时间戳,如果注册了callback,vsync_callback_hook_,就会执行回调函数。
本文具体看driver是如何工作,不对VSyncWorker过多分析。
drmVBlank的实现
typedef struct _drmVBlankReq {
drmVBlankSeqType type;
unsigned int sequence;
unsigned long signal;
} drmVBlankReq, *drmVBlankReqPtr;
typedef struct _drmVBlankReply {
drmVBlankSeqType type;
unsigned int sequence;
long tval_sec;
long tval_usec;
} drmVBlankReply, *drmVBlankReplyPtr;
typedef union _drmVBlank {
drmVBlankReq request;
drmVBlankReply reply;
} drmVBlank, *drmVBlankPtr;
等vsync的时候,填入drmVBlankReq,sequence,type;
sequence=1:会在drm driver中使用
type:这里用的是DRM_VBLANK_RELATIVE,相对时间;high_crtc,用于判断要读取哪个crtc的vsync
调用 ret = ioctl(fd, DRM_IOCTL_WAIT_VBLANK, vbl);
从drm_ioctl.c中可以看到drm中会调用
int drm_wait_vblank_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct drm_crtc *crtc;
struct drm_vblank_crtc *vblank;
union drm_wait_vblank *vblwait = data;
int ret;
u64 req_seq, seq;
flags = vblwait->request.type & _DRM_VBLANK_FLAGS_MASK;
high_pipe = (vblwait->request.type & _DRM_VBLANK_HIGH_CRTC_MASK);
pipe_index = high_pipe >> _DRM_VBLANK_HIGH_CRTC_SHIFT;
if (pipe >= dev->num_crtcs)
return -EINVAL;
vblank = &dev->vblank[pipe];
seq = drm_vblank_count(dev, pipe);
switch (vblwait->request.type & _DRM_VBLANK_TYPES_MASK) {
case _DRM_VBLANK_RELATIVE:
req_seq = seq + vblwait->request.sequence;
vblwait->request.sequence = req_seq;
vblwait->request.type &= ~_DRM_VBLANK_RELATIVE;
break;
case _DRM_VBLANK_ABSOLUTE:
req_seq = widen_32_to_64(vblwait->request.sequence, seq);
break;
default:
ret = -EINVAL;
goto done;
}
if (flags & _DRM_VBLANK_EVENT) {
return drm_queue_vblank_event(dev, pipe, req_seq, vblwait, file_priv);
}
if (req_seq != seq) {
int wait;
wait = wait_event_interruptible_timeout(vblank->queue,
drm_vblank_passed(drm_vblank_count(dev, pipe), req_seq) ||
!READ_ONCE(vblank->enabled),
msecs_to_jiffies(3000));
}
}
先来看下vblank count是怎么更新的。
drm用vblank来抽象vsync,vsync是display模块产生的,正常情况下开启后会按照一定时间触发中断。
在各家vendor实现的drm driver中会注册vsync的中断服务程序,便于软件进行处理异常,包括vsync
比如rockchip
kernel\drivers\gpu\drm\rockchip\rockchip_drm_vop.c
static int vop_bind(struct device *dev, struct device *master, void *data)
{
... ...
ret = devm_request_irq(dev, vop->irq, vop_isr,
IRQF_SHARED, dev_name(dev), vop);
... ...
}
来看下中断处理函数
static irqreturn_t vop_isr(int irq, void *data)
{
... ...
if (active_irqs & FS_INTR) {
drm_crtc_handle_vblank(crtc);
vop_handle_vblank(vop);
active_irqs &= ~FS_INTR;
ret = IRQ_HANDLED;
}
... ...
}
这个vblank的函数比较耀眼,来具体看下
看到store_vblank里会把vblank->count+1; 然后会唤醒等待队列。
中断里把vblank count+1;中断是display模块产生的,如果刷新率为60帧,每16.6ms就会来一个中断,这个操作与user space无关。
drm_wait_vblank_ioctl等到vblank count之后就会唤醒进程,并返回给user。
用一张简图总结下大致流程: