I noticed X events does not appear immediately to xcb_poll_for_event
. So in my application-loop, after user opens a new window a frame needs to pass before update
function gets any events, which cause bad flashing effects as there is a time gap between window and it's content getting drawn.
while (!done) {
/* FRAME_TIME_NS = 200000000L */
next_frame_time += FRAME_TIME_NS;
/* Wait for client semaphore to be posted */
if (sem_trywait(&p_shmbuf->sem1) == 0) {
if (p_shmbuf->buf[0]) {
open_window(p_shmbuf->buf);
memset(p_shmbuf->buf, 0, sizeof(p_shmbuf->buf));
/* Tell peer the buffer is accessible to write */
sem_post(&p_shmbuf->sem2);
}
} else if (!(errno & EAGAIN))
bug("sem_trywait-sem1", errno);
/* Handle X event loop */
update();
/* Slow frequency of reading user messages */
struct timespec ts;
long now = current_time_ns();
long sleep_time = next_frame_time - now;
if (sleep_time > 0) {
ts.tv_sec = sleep_time / 1000000000L;
ts.tv_nsec = sleep_time % 1000000000L;
nanosleep(&ts, NULL);
}
}
To safely block a thread while waiting for an event, I send an intentionally failing event and stop waiting once the error or other event is catched. I was thinking the update function should then get in time to poll other events.
static xcb_generic_event_t *
sync_event_loop()
{
xcb_generic_event_t *event = NULL;
/* Dummy request to trigger an error */
xcb_void_cookie_t sync_cookie =
xcb_map_window(x_conn.conn, (xcb_window_t)0xDEADBEEF);
xcb_flush(x_conn.conn);
while ((event = xcb_wait_for_event(x_conn.conn)) != NULL) {
if ((event->response_type & 0x7f) == 0) {
xcb_generic_error_t *err = (xcb_generic_error_t *)event;
if (err->sequence == sync_cookie.sequence) {
/* Got expected error */
free(err);
break;
} else {
/* Some other error */
free(err);
}
} else {
/* Send event to event loop */
return event;
}
}
return NULL;
}
And in update
function I continue polling the rest of queued events:
void
unxnote_update()
{
bool update = false;
xcb_generic_event_t *event;
event = sync_event_loop();
while (event || (event = xcb_poll_for_event(x_conn.conn)) != NULL) {
update = true;
switch (event->response_type & ~0x80) {
case XCB_EXPOSE:
draw_context(((xcb_expose_event_t *)event)->window);
break;
case XCB_BUTTON_PRESS:
close_window(xcb_button_press_event_t *)event)->event);
}
free(event);
event = NULL;
};
if (update)
xcb_flush(x_conn.conn);
}
For some reason the synchronization happens for the first event but rest of events gets "out of sync", meaning they are drawn a frame late.
Where have I made a mistake in my implementation? Also, is there a better way of doing this?
Where have I made a mistake in my implementation?
So, if I understand correctly, your main loop sleeps:
struct timespec ts;
long now = current_time_ns();
long sleep_time = next_frame_time - now;
if (sleep_time > 0) {
ts.tv_sec = sleep_time / 1000000000L;
ts.tv_nsec = sleep_time % 1000000000L;
nanosleep(&ts, NULL);
}
You try to handle synchronisation by checking for X11 events before you sleep. But what happens if you were to sleep for 100 ms and an event arrives 1 ms after you start to sleep? You are waiting 99 ms before you handle the event!
I guess that is the delay that you are seeing.
I do not fully understand your clever synchronisation mechanism, but it is always possible that an event arrives while your main loop is sleeping.
Also, is there a better way of doing this?
Do not sleep. Always handle X11 events.
Specifically: Use xcb_get_file_descriptor()
to get the FD that xcb uses to talk to the X11 server. Then, instead of nanosleep
, use e.g. poll
to sleep while at the same time waiting for the FD to become readable. Before sleeping, handle all events that libxcb might already have read via xcb_poll_for_pending_event()
.
Pseudocode:
start:
while (event = xcb_poll_for_pending_event(conn)) {
// handle event
}
int timeout = /* how long to wait at most; you will have to compute this based on the current time */
struct pollfd pollfd[1];
pollfd[0].fd = xcb_get_file_descriptor(conn);
pollfd[0].events = POLLIN;
int poll_result = poll(&pollfd[0], 1, timeout);
if (poll_result > 0) {
// There is an incoming event from the X11 server. Handle it and start over. E.g.
goto start; // I am lazy. But do not really use goto!
} else if (poll_result == 0) {
// Timeout!
} else {
// Poll returned an error?! That should not happen.
}
which cause bad flashing effects as there is a time gap between window and it's content getting drawn.
I do not quite understand what you describe here, but the usual reason for flashing background is that you tell the X11 server to draw some background and only later (in Expose event handling) do you draw the actual window contents. Do net set a background on your windows.