From 79bf7cd5c31be8d12ca706b2d1a1857ab74d415b Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 18 Dec 2023 11:00:35 +0000 Subject: [PATCH] vblank: use table of function pointers instead of switch Makes conditional compilation easier in the future. Signed-off-by: Yuxuan Shui --- src/vblank.c | 207 ++++++++++++++++++++++++++++----------------------- 1 file changed, 112 insertions(+), 95 deletions(-) diff --git a/src/vblank.c b/src/vblank.c index 4fbf526..31f7cc1 100644 --- a/src/vblank.c +++ b/src/vblank.c @@ -21,18 +21,19 @@ struct vblank_callback { #define VBLANK_WIND_DOWN 4 struct vblank_scheduler { - enum vblank_scheduler_type type; - xcb_window_t target_window; struct x_connection *c; size_t callback_capacity, callback_count; + struct vblank_callback *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; - struct vblank_callback *callbacks; - struct ev_loop *loop; + xcb_window_t target_window; + enum vblank_scheduler_type type; + bool vblank_event_requested; }; struct present_vblank_scheduler { @@ -42,49 +43,27 @@ struct present_vblank_scheduler { /// The timestamp for the end of last vblank. uint64_t last_ust; ev_timer callback_timer; - bool vblank_event_requested; xcb_present_event_t event_id; xcb_special_event_t *event; }; -static void present_vblank_scheduler_schedule(struct present_vblank_scheduler *sched) { - log_verbose("Requesting vblank event for window 0x%08x, msc %" PRIu64, - sched->base.target_window, sched->last_msc + 1); - assert(!sched->vblank_event_requested); - x_request_vblank_event(sched->base.c, sched->base.target_window, sched->last_msc + 1); - sched->vblank_event_requested = true; -} - -static void vblank_scheduler_schedule_internal(struct vblank_scheduler *self) { - switch (self->type) { - case PRESENT_VBLANK_SCHEDULER: - return present_vblank_scheduler_schedule((struct present_vblank_scheduler *)self); - case SGI_VIDEO_VSYNC_VBLANK_SCHEDULER: - case LAST_VBLANK_SCHEDULER: - default: assert(false); - } -} +struct vblank_scheduler_ops { + void (*init)(struct vblank_scheduler *self); + void (*deinit)(struct vblank_scheduler *self); + void (*schedule)(struct vblank_scheduler *self); + bool (*handle_x_events)(struct vblank_scheduler *self); +}; static void -vblank_scheduler_invoke_callbacks(struct vblank_scheduler *self, struct vblank_event *event) { - // callbacks might be added during callback invocation, so we need to - // copy the callback_count. - size_t count = self->callback_count; - if (count == 0) { - self->wind_down--; - } else { - self->wind_down = VBLANK_WIND_DOWN; - } - for (size_t i = 0; i < count; i++) { - self->callbacks[i].fn(event, self->callbacks[i].user_data); - } - // remove the callbacks that we have called, keep the newly added ones. - memmove(self->callbacks, self->callbacks + count, - (self->callback_count - count) * sizeof(*self->callbacks)); - self->callback_count -= count; - if (self->callback_count || self->wind_down) { - vblank_scheduler_schedule_internal(self); - } +vblank_scheduler_invoke_callbacks(struct vblank_scheduler *self, struct vblank_event *event); + +static void present_vblank_scheduler_schedule(struct vblank_scheduler *base) { + auto self = (struct present_vblank_scheduler *)base; + log_verbose("Requesting vblank event for window 0x%08x, msc %" PRIu64, + base->target_window, self->last_msc + 1); + assert(!base->vblank_event_requested); + x_request_vblank_event(base->c, base->target_window, self->last_msc + 1); + base->vblank_event_requested = true; } static void present_vblank_callback(EV_P attr_unused, ev_timer *w, int attr_unused revents) { @@ -93,7 +72,7 @@ static void present_vblank_callback(EV_P attr_unused, ev_timer *w, int attr_unus .msc = sched->last_msc, .ust = sched->last_ust, }; - sched->vblank_event_requested = false; + sched->base.vblank_event_requested = false; vblank_scheduler_invoke_callbacks(&sched->base, &event); } @@ -120,50 +99,6 @@ static void present_vblank_scheduler_deinit(struct vblank_scheduler *base) { xcb_unregister_for_special_event(base->c->c, self->event); } -void vblank_scheduler_free(struct vblank_scheduler *self) { - switch (self->type) { - case PRESENT_VBLANK_SCHEDULER: present_vblank_scheduler_deinit(self); break; - case SGI_VIDEO_VSYNC_VBLANK_SCHEDULER: break; - case LAST_VBLANK_SCHEDULER: - default: assert(false); - } - free(self->callbacks); - free(self); -} - -struct vblank_scheduler *vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c, - xcb_window_t target_window) { - struct vblank_scheduler *self = calloc(1, sizeof(struct present_vblank_scheduler)); - self->target_window = target_window; - self->c = c; - self->loop = loop; - present_vblank_scheduler_init(self); - return self; -} - -bool vblank_scheduler_schedule(struct vblank_scheduler *self, - vblank_callback_t vblank_callback, void *user_data) { - if (self->callback_count == 0 && self->wind_down == 0) { - vblank_scheduler_schedule_internal(self); - } - if (self->callback_count == self->callback_capacity) { - size_t new_capacity = - self->callback_capacity ? self->callback_capacity * 2 : 1; - void *new_buffer = - realloc(self->callbacks, new_capacity * sizeof(*self->callbacks)); - if (!new_buffer) { - return false; - } - self->callbacks = new_buffer; - self->callback_capacity = new_capacity; - } - self->callbacks[self->callback_count++] = (struct vblank_callback){ - .fn = vblank_callback, - .user_data = user_data, - }; - return true; -} - /// Handle PresentCompleteNotify events /// /// Schedule the registered callback to be called when the current vblank ends. @@ -175,7 +110,7 @@ static void handle_present_complete_notify(struct present_vblank_scheduler *self return; } - assert(self->vblank_event_requested); + assert(self->base.vblank_event_requested); // X sometimes sends duplicate/bogus MSC events, when screen has just been turned // off. Don't use the msc value in these events. We treat this as not receiving a @@ -213,9 +148,10 @@ static void handle_present_complete_notify(struct present_vblank_scheduler *self ev_timer_start(self->base.loop, &self->callback_timer); } -static bool handle_present_events(struct present_vblank_scheduler *self) { +static bool handle_present_events(struct vblank_scheduler *base) { + auto self = (struct present_vblank_scheduler *)base; xcb_present_generic_event_t *ev; - while ((ev = (void *)xcb_poll_for_special_event(self->base.c->c, self->event))) { + while ((ev = (void *)xcb_poll_for_special_event(base->c->c, self->event))) { if (ev->event != self->event_id) { // This event doesn't have the right event context, it's not meant // for us. @@ -230,13 +166,94 @@ static bool handle_present_events(struct present_vblank_scheduler *self) { } return true; } + +static const struct vblank_scheduler_ops vblank_scheduler_ops[LAST_VBLANK_SCHEDULER] = { + [PRESENT_VBLANK_SCHEDULER] = + { + .init = present_vblank_scheduler_init, + .deinit = present_vblank_scheduler_deinit, + .schedule = present_vblank_scheduler_schedule, + .handle_x_events = handle_present_events, + }, +}; + +static void vblank_scheduler_schedule_internal(struct vblank_scheduler *self) { + assert(self->type < LAST_VBLANK_SCHEDULER); + auto fn = vblank_scheduler_ops[self->type].schedule; + assert(fn != NULL); + fn(self); +} + +bool vblank_scheduler_schedule(struct vblank_scheduler *self, + vblank_callback_t vblank_callback, void *user_data) { + if (self->callback_count == 0 && self->wind_down == 0) { + vblank_scheduler_schedule_internal(self); + } + if (self->callback_count == self->callback_capacity) { + size_t new_capacity = + self->callback_capacity ? self->callback_capacity * 2 : 1; + void *new_buffer = + realloc(self->callbacks, new_capacity * sizeof(*self->callbacks)); + if (!new_buffer) { + return false; + } + self->callbacks = new_buffer; + self->callback_capacity = new_capacity; + } + self->callbacks[self->callback_count++] = (struct vblank_callback){ + .fn = vblank_callback, + .user_data = user_data, + }; + return true; +} + +static void +vblank_scheduler_invoke_callbacks(struct vblank_scheduler *self, struct vblank_event *event) { + // callbacks might be added during callback invocation, so we need to + // copy the callback_count. + size_t count = self->callback_count; + if (count == 0) { + self->wind_down--; + } else { + self->wind_down = VBLANK_WIND_DOWN; + } + for (size_t i = 0; i < count; i++) { + self->callbacks[i].fn(event, self->callbacks[i].user_data); + } + // remove the callbacks that we have called, keep the newly added ones. + memmove(self->callbacks, self->callbacks + count, + (self->callback_count - count) * sizeof(*self->callbacks)); + self->callback_count -= count; + if (self->callback_count || self->wind_down) { + vblank_scheduler_schedule_internal(self); + } +} + +void vblank_scheduler_free(struct vblank_scheduler *self) { + assert(self->type < LAST_VBLANK_SCHEDULER); + auto fn = vblank_scheduler_ops[self->type].deinit; + if (fn != NULL) { + fn(self); + } + free(self->callbacks); + free(self); +} + +struct vblank_scheduler *vblank_scheduler_new(struct ev_loop *loop, struct x_connection *c, + xcb_window_t target_window) { + struct vblank_scheduler *self = calloc(1, sizeof(struct present_vblank_scheduler)); + self->target_window = target_window; + self->c = c; + self->loop = loop; + vblank_scheduler_ops[PRESENT_VBLANK_SCHEDULER].init(self); + return self; +} + bool vblank_handle_x_events(struct vblank_scheduler *self) { - switch (self->type) { - case PRESENT_VBLANK_SCHEDULER: - return handle_present_events((struct present_vblank_scheduler *)self); - case SGI_VIDEO_VSYNC_VBLANK_SCHEDULER: return true; - case LAST_VBLANK_SCHEDULER: - default: assert(false); + assert(self->type < LAST_VBLANK_SCHEDULER); + auto fn = vblank_scheduler_ops[self->type].handle_x_events; + if (fn != NULL) { + return fn(self); } return true; } \ No newline at end of file