vblank: winding down vblank events instead of stopping immediately

I noticed sometimes full frame rate video is rendered at half frame rate
sometimes. That is because the damage notify is sent very close to
vblank, and since we request vblank events when we get the damage, we
miss the vblank event immediately after, despite the damage happening
before the vblank.

       request  next  ......  next next
damage  vblank vblank          vblank
   |    |       |     ......      |
   v    v       v                 v
---------------------->>>>>>---------

`request vblank` is triggered by `damage`, but because it's too close to
`next vblank`, that vblank is missed despite we requested before it
happening, and we only get `next next vblank`. The result is we will
drop every other frame.

The solution in this commit is that we will keep requesting vblank
events, right after we received a vblank event, even when nobody is
asking for them. We would do that for a set number of vblanks before
stopping (currently 4).

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
This commit is contained in:
Yuxuan Shui 2023-12-19 23:17:09 +00:00
parent 25d16b0352
commit c42e6cef0a
No known key found for this signature in database
GPG Key ID: D3A4405BE6CC17F4
1 changed files with 15 additions and 2 deletions

View File

@ -18,11 +18,19 @@ struct vblank_closure {
void *user_data;
};
#define VBLANK_WIND_DOWN 4
struct vblank_scheduler {
struct x_connection *c;
size_t callback_capacity, callback_count;
struct vblank_closure *callbacks;
struct ev_loop *loop;
/// Request extra vblank events even when no callbacks are scheduled.
/// This is because when callbacks are scheduled too close to a vblank,
/// we might send PresentNotifyMsc request too late and miss the vblank event.
/// So we request extra vblank events right after the last vblank event
/// to make sure this doesn't happen.
unsigned int wind_down;
xcb_window_t target_window;
enum vblank_scheduler_type type;
bool vblank_event_requested;
@ -178,7 +186,7 @@ static void vblank_scheduler_schedule_internal(struct vblank_scheduler *self) {
bool vblank_scheduler_schedule(struct vblank_scheduler *self,
vblank_callback_t vblank_callback, void *user_data) {
if (self->callback_count == 0) {
if (self->callback_count == 0 && self->wind_down == 0) {
vblank_scheduler_schedule_internal(self);
}
if (self->callback_count == self->callback_capacity) {
@ -204,6 +212,11 @@ vblank_scheduler_invoke_callbacks(struct vblank_scheduler *self, struct vblank_e
// callbacks might be added during callback invocation, so we need to
// copy the callback_count.
size_t count = self->callback_count, write_head = 0;
if (count == 0) {
self->wind_down--;
} else {
self->wind_down = VBLANK_WIND_DOWN;
}
for (size_t i = 0; i < count; i++) {
auto action = self->callbacks[i].fn(event, self->callbacks[i].user_data);
switch (action) {
@ -222,7 +235,7 @@ vblank_scheduler_invoke_callbacks(struct vblank_scheduler *self, struct vblank_e
assert(count == self->callback_count && "callbacks should not be added when "
"callbacks are being invoked.");
self->callback_count = write_head;
if (self->callback_count) {
if (self->callback_count || self->wind_down) {
vblank_scheduler_schedule_internal(self);
}
}