From 30d9f5392fb07105e792963d74786024adc79dd5 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 25 Aug 2023 03:06:42 +0200 Subject: Add monitor events, limit fps to monitor the window is in, reduce latency (glFinish, fps limit) --- src/window/window.c | 376 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 360 insertions(+), 16 deletions(-) (limited to 'src/window') diff --git a/src/window/window.c b/src/window/window.c index 980e811..8035239 100644 --- a/src/window/window.c +++ b/src/window/window.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -14,6 +15,7 @@ #include /* TODO: Handle XIM better. Set XIM position to text position on screen (for text input) and reset input when selecting a new text input, etc */ +/* TODO: Separate events from windows. Especially when it comes to monitor events */ /* Should be in range [2,] */ #define MAX_STACKED_EVENTS 32 @@ -51,6 +53,8 @@ static bool x11_events_circular_buffer_pop(x11_events_circular_buffer *self, mgl return true; } +#define MAX_MONITORS 12 + typedef struct { GLXContext glx_context; XIM xim; @@ -74,6 +78,11 @@ typedef struct { GLXFBConfig *fbconfigs; GLXFBConfig fbconfig; XVisualInfo *visual_info; + + /* This only contains connected and active monitors */ + mgl_monitor monitors[MAX_MONITORS]; + int num_monitors; + /* Used to stack text event on top of key press/release events and other text events. For example pressing a key should give the user both key press and text events @@ -163,6 +172,9 @@ static int x11_context_init(x11_context *self, bool alpha) { self->fbconfig = NULL; self->visual_info = NULL; + memset(self->monitors, 0, sizeof(self->monitors)); + self->num_monitors = 0; + if(!glx_context_choose(context, &self->fbconfigs, &self->fbconfig, &self->visual_info, alpha)) { x11_context_deinit(self); return -1; @@ -176,8 +188,10 @@ static int x11_context_init(x11_context *self, bool alpha) { const char data[1] = {0}; Pixmap blank_bitmap = XCreateBitmapFromData(context->connection, DefaultRootWindow(context->connection), data, 1, 1); - if(!blank_bitmap) + if(!blank_bitmap) { + x11_context_deinit(self); return -1; + } XColor dummy; self->invisible_cursor = XCreatePixmapCursor(context->connection, blank_bitmap, blank_bitmap, &dummy, &dummy, 0, 0); @@ -195,6 +209,15 @@ static int x11_context_init(x11_context *self, bool alpha) { void x11_context_deinit(x11_context *self) { mgl_context *context = mgl_get_context(); + for(int i = 0; i < self->num_monitors; ++i) { + mgl_monitor *monitor = &self->monitors[i]; + if(monitor->name) { + free((char*)monitor->name); + monitor->name = NULL; + } + } + self->num_monitors = 0; + if(self->color_map) { XFreeColormap(context->connection, self->color_map); self->color_map = None; @@ -246,6 +269,140 @@ static bool x11_context_pop_event(x11_context *self, mgl_event *event) { return x11_events_circular_buffer_pop(&self->events, event); } +static mgl_monitor* x11_context_add_monitor(x11_context *self, RROutput output_id, RRCrtc crtc_id, const char *name, mgl_vec2i pos, mgl_vec2i size, int refresh_rate) { + if(self->num_monitors == MAX_MONITORS) + return NULL; + + mgl_monitor *monitor = &self->monitors[self->num_monitors]; + monitor->id = output_id; + monitor->crtc_id = crtc_id; + monitor->name = strdup(name); + if(!monitor->name) + return NULL; + monitor->pos = pos; + monitor->size = size; + monitor->refresh_rate = refresh_rate; + self->num_monitors++; + + return monitor; +} + +static mgl_monitor* x11_context_get_monitor_by_id(x11_context *self, RROutput output_id) { + for(int i = 0; i < self->num_monitors; ++i) { + mgl_monitor *monitor = &self->monitors[i]; + if(monitor->id == (int)output_id) + return monitor; + } + return NULL; +} + +static mgl_monitor* x11_context_get_monitor_by_crtc_id(x11_context *self, RRCrtc crtc_id) { + for(int i = 0; i < self->num_monitors; ++i) { + mgl_monitor *monitor = &self->monitors[i]; + if(monitor->crtc_id == (int)crtc_id) + return monitor; + } + return NULL; +} + +static bool x11_context_remove_monitor(x11_context *self, RROutput output_id, mgl_event *event) { + int index_to_remove = -1; + for(int i = 0; i < self->num_monitors; ++i) { + mgl_monitor *monitor = &self->monitors[i]; + if(monitor->id == (int)output_id) { + index_to_remove = i; + break; + } + } + + if(index_to_remove == -1) + return false; + + mgl_monitor *monitor = &self->monitors[index_to_remove]; + free((char*)monitor->name); + monitor->name = NULL; + + for(int i = index_to_remove + 1; i < self->num_monitors; ++i) { + self->monitors[i - 1] = self->monitors[i]; + } + self->num_monitors--; + + event->monitor_disconnected.id = output_id; + return true; +} + +static int round_int(double value) { + return value + 0.5; +} + +static int monitor_info_get_framerate(const XRRModeInfo *mode_info) { + double v_total = mode_info->vTotal; + if(mode_info->modeFlags & RR_DoubleScan) { + v_total *= 2; + } + + if(mode_info->modeFlags & RR_Interlace) { + v_total /= 2; + } + + if(mode_info->hTotal > 0 && v_total > 0.0001) { + return round_int((double)mode_info->dotClock / ((double)mode_info->hTotal * v_total)); + } else { + return 0; + } +} + +static const XRRModeInfo* get_mode_info(const XRRScreenResources *sr, RRMode id) { + for(int i = 0; i < sr->nmode; ++i) { + if(sr->modes[i].id == id) + return &sr->modes[i]; + } + return NULL; +} + +typedef void (*active_monitor_callback)(const mgl_monitor *monitor, void *userdata); +static void for_each_active_monitor_output(Display *display, active_monitor_callback callback, void *userdata) { + XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display)); + if(!screen_res) + return; + + char display_name[256]; + for(int i = 0; i < screen_res->noutput; ++i) { + XRROutputInfo *out_info = XRRGetOutputInfo(display, screen_res, screen_res->outputs[i]); + if(out_info && out_info->crtc && out_info->connection == RR_Connected) { + XRRCrtcInfo *crt_info = XRRGetCrtcInfo(display, screen_res, out_info->crtc); + if(crt_info && crt_info->mode) { + const XRRModeInfo *mode_info = get_mode_info(screen_res, crt_info->mode); + if(mode_info && out_info->nameLen < (int)sizeof(display_name)) { + memcpy(display_name, out_info->name, out_info->nameLen); + display_name[out_info->nameLen] = '\0'; + + mgl_monitor monitor = { + .id = screen_res->outputs[i], + .crtc_id = out_info->crtc, + .name = display_name, + .pos = { .x = crt_info->x, .y = crt_info->y }, + .size = { .x = (int)crt_info->width, .y = (int)crt_info->height }, + .refresh_rate = monitor_info_get_framerate(mode_info), + }; + callback(&monitor, userdata); + } + } + if(crt_info) + XRRFreeCrtcInfo(crt_info); + } + if(out_info) + XRRFreeOutputInfo(out_info); + } + + XRRFreeScreenResources(screen_res); +} + +static void monitor_callback_add_to_x11_context(const mgl_monitor *monitor, void *userdata) { + x11_context *x11_context = userdata; + x11_context_add_monitor(x11_context, monitor->id, monitor->crtc_id, monitor->name, monitor->pos, monitor->size, monitor->refresh_rate); +} + /* TODO: Use gl OML present for other platforms than nvidia? nvidia doesn't support present yet */ /* TODO: check for glx swap control extension string (GLX_EXT_swap_control, etc) */ @@ -271,6 +428,34 @@ static void set_vertical_sync_enabled(Window window, int enabled) { fprintf(stderr, "mgl warning: setting vertical sync failed\n"); } +static void mgl_window_set_frame_time_limit_monitor(mgl_window *self) { + int monitor_refresh_rate = 0; + mgl_vec2i window_center = (mgl_vec2i) { self->pos.x + self->size.x / 2, self->pos.y + self->size.y / 2 }; + for(int i = 0; i < self->num_monitors; ++i) { + mgl_monitor *monitor = &self->monitors[i]; + if(window_center.x >= monitor->pos.x && window_center.x <= monitor->pos.x + monitor->size.x + && window_center.y >= monitor->pos.y && window_center.y <= monitor->pos.y + monitor->size.y) + { + monitor_refresh_rate = monitor->refresh_rate; + break; + } + } + + if(monitor_refresh_rate == 0 && self->num_monitors > 0) + monitor_refresh_rate = self->monitors[0].refresh_rate; + + if(monitor_refresh_rate == 0) + monitor_refresh_rate = 60; + + self->frame_time_limit_monitor = 1.0 / (double)monitor_refresh_rate; +} + +static void mgl_window_on_move(mgl_window *self, int x, int y) { + self->pos.x = x; + self->pos.y = y; + mgl_window_set_frame_time_limit_monitor(self); +} + static void mgl_window_on_resize(mgl_window *self, int width, int height) { self->size.x = width; self->size.y = height; @@ -295,9 +480,13 @@ static int mgl_window_init(mgl_window *self, const char *title, const mgl_window self->focused = false; self->key_repeat_enabled = true; self->frame_time_limit = 0.0; + self->frame_time_limit_monitor = 0.0; mgl_clock_init(&self->frame_timer); self->clipboard_string = NULL; self->clipboard_size = 0; + self->monitors = NULL; + self->num_monitors = 0; + self->pos = (mgl_vec2i){ 0, 0 }; mgl_vec2i window_size = params ? params->size : (mgl_vec2i){ 0, 0 }; if(window_size.x <= 0 || window_size.y <= 0) { @@ -306,6 +495,8 @@ static int mgl_window_init(mgl_window *self, const char *title, const mgl_window } self->size = window_size; + mgl_vec2i window_pos = params ? params->position : (mgl_vec2i){ 0, 0 }; + self->context = malloc(sizeof(x11_context)); if(!self->context) { fprintf(stderr, "mgl error: failed to allocate x11 context\n"); @@ -319,6 +510,9 @@ static int mgl_window_init(mgl_window *self, const char *title, const mgl_window return -1; } + self->monitors = x11_context->monitors; + self->num_monitors = 0; + mgl_context *context = mgl_get_context(); Window parent_window = params ? params->parent_window : None; @@ -360,11 +554,15 @@ static int mgl_window_init(mgl_window *self, const char *title, const mgl_window return -1; } + if(params && params->size.x > 0 && params->size.y > 0) { + XResizeWindow(context->connection, existing_window, params->size.x, params->size.y); + } + self->window = existing_window; if(hide_window) XUnmapWindow(context->connection, existing_window); } else { - self->window = XCreateWindow(context->connection, parent_window, params->position.x, params->position.y, + self->window = XCreateWindow(context->connection, parent_window, window_pos.x, window_pos.y, window_size.x, window_size.y, 0, x11_context->visual_info->depth, InputOutput, x11_context->visual_info->visual, CWColormap | CWEventMask | CWOverrideRedirect | CWBorderPixel | CWBackPixel | CWBitGravity, &window_attr); @@ -421,8 +619,6 @@ static int mgl_window_init(mgl_window *self, const char *title, const mgl_window unsigned int dummy_u; XQueryPointer(context->connection, self->window, &dummy_w, &dummy_w, &dummy_i, &dummy_i, &self->cursor_position.x, &self->cursor_position.y, &dummy_u); - mgl_window_on_resize(self, self->size.x, self->size.y); - x11_context->xim = XOpenIM(context->connection, NULL, NULL, NULL); if(!x11_context->xim) { fprintf(stderr, "mgl error: XOpenIM failed\n"); @@ -437,6 +633,12 @@ static int mgl_window_init(mgl_window *self, const char *title, const mgl_window return -1; } + for_each_active_monitor_output(context->connection, monitor_callback_add_to_x11_context, x11_context); + self->num_monitors = x11_context->num_monitors; + + mgl_window_on_resize(self, self->size.x, self->size.y); + mgl_window_on_move(self, window_pos.x, window_pos.y); + self->open = true; self->focused = false; /* TODO: Check if we need to call XGetInputFocus for this, or just wait for focus event */ return 0; @@ -590,7 +792,7 @@ static mgl_mouse_button x11_button_to_mgl_button(unsigned int button) { return MGL_BUTTON_UNKNOWN; } -static void mgl_window_handle_key_event(mgl_window *self, XKeyEvent *xkey, mgl_event *event, mgl_context *context, bool pressed) { +static void mgl_window_handle_key_event(mgl_window *self, XKeyEvent *xkey, mgl_event *event, mgl_context *context) { event->key.code = x11_keysym_to_mgl_key(XKeycodeToKeysym(context->connection, xkey->keycode, 0)); event->key.alt = ((xkey->state & Mod1Mask) != 0); event->key.control = ((xkey->state & ControlMask) != 0); @@ -638,11 +840,146 @@ static void mgl_window_handle_text_event(mgl_window *self, XEvent *xev) { } } +static bool mgl_on_monitor_added(Display *display, x11_context *x11_context, XRROutputChangeNotifyEvent *rr_output_change_event, XRRScreenResources *screen_res, RROutput output_id, XRROutputInfo *out_info, mgl_event *event) { + char display_name[256]; + mgl_monitor *monitor = NULL; + XRRCrtcInfo *crt_info = NULL; + const XRRModeInfo *mode_info = NULL; + + if(!rr_output_change_event->mode) + return false; + + if(x11_context_get_monitor_by_id(x11_context, output_id)) + return false; + + if(out_info->nameLen >= (int)sizeof(display_name)) + return false; + + crt_info = XRRGetCrtcInfo(display, screen_res, out_info->crtc); + if(!crt_info) + goto done; + + mode_info = get_mode_info(screen_res, rr_output_change_event->mode); + if(!mode_info) + goto done; + + memcpy(display_name, out_info->name, out_info->nameLen); + display_name[out_info->nameLen] = '\0'; + + monitor = x11_context_add_monitor(x11_context, output_id, out_info->crtc, display_name, + (mgl_vec2i){ .x = crt_info->x, .y = crt_info->y }, + (mgl_vec2i){ .x = (int)crt_info->width, .y = (int)crt_info->height }, + monitor_info_get_framerate(mode_info)); + + if(!monitor) + goto done; + + event->monitor_connected.id = monitor->id; + event->monitor_connected.name = monitor->name; + event->monitor_connected.x = monitor->pos.x; + event->monitor_connected.y = monitor->pos.y; + event->monitor_connected.width = monitor->size.x; + event->monitor_connected.height = monitor->size.y; + event->monitor_connected.refresh_rate = monitor->refresh_rate; + + done: + if(crt_info) + XRRFreeCrtcInfo(crt_info); + return monitor != NULL; +} + +static bool mgl_on_monitor_state_changed(Display *display, x11_context *x11_context, XRROutputChangeNotifyEvent *rr_output_change_event, mgl_event *event) { + bool state_changed = false; + XRROutputInfo *out_info = NULL; + + if(!rr_output_change_event->output) + return false; + + XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display)); + if(!screen_res) + return false; + + out_info = XRRGetOutputInfo(display, screen_res, rr_output_change_event->output); + if(out_info && out_info->crtc && out_info->connection == RR_Connected) { + state_changed = mgl_on_monitor_added(display, x11_context, rr_output_change_event, screen_res, rr_output_change_event->output, out_info, event); + } else { + state_changed = x11_context_remove_monitor(x11_context, rr_output_change_event->output, event); + } + + if(out_info) + XRRFreeOutputInfo(out_info); + + XRRFreeScreenResources(screen_res); + return state_changed; +} + +static bool mgl_on_monitor_property_changed(Display *display, x11_context *x11_context, XRRCrtcChangeNotifyEvent *rr_crtc_change_event, mgl_event *event) { + if(!rr_crtc_change_event->crtc) + return false; + + mgl_monitor *monitor = x11_context_get_monitor_by_crtc_id(x11_context, rr_crtc_change_event->crtc); + if(!monitor) + return false; + + XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display)); + if(!screen_res) + return false; + + monitor->pos = (mgl_vec2i){ .x = rr_crtc_change_event->x, .y = rr_crtc_change_event->y }; + monitor->size = (mgl_vec2i){ .x = rr_crtc_change_event->width, .y = rr_crtc_change_event->height }; + const XRRModeInfo *mode_info = get_mode_info(screen_res, rr_crtc_change_event->mode); + if(mode_info) + monitor->refresh_rate = monitor_info_get_framerate(mode_info); + + XRRFreeScreenResources(screen_res); + + event->monitor_property_changed.id = monitor->id; + event->monitor_property_changed.name = monitor->name; + event->monitor_property_changed.x = monitor->pos.x; + event->monitor_property_changed.y = monitor->pos.y; + event->monitor_property_changed.width = monitor->size.x; + event->monitor_property_changed.height = monitor->size.y; + event->monitor_property_changed.refresh_rate = monitor->refresh_rate; + return true; +} + +/* Returns true if an event was generated */ +static bool mgl_on_rr_notify(mgl_context *context, x11_context *x11_context, XEvent *xev, int subtype, mgl_event *event) { + switch(subtype) { + case RRNotify_CrtcChange: { + XRRCrtcChangeNotifyEvent *rr_crtc_change_event = (XRRCrtcChangeNotifyEvent*)xev; + return mgl_on_monitor_property_changed(context->connection, x11_context, rr_crtc_change_event, event); + } + case RRNotify_OutputChange: { + XRROutputChangeNotifyEvent *rr_output_change_event = (XRROutputChangeNotifyEvent*)xev; + return mgl_on_monitor_state_changed(context->connection, x11_context, rr_output_change_event, event); + } + } + return false; +} + static void mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event *event, mgl_context *context) { + x11_context *x11_context = self->context; + switch(xev->type - context->randr_event_base) { + case RRScreenChangeNotify: { + XRRUpdateConfiguration(xev); + event->type = MGL_EVENT_UNKNOWN; + return; + } + case RRNotify: { + XRRNotifyEvent *rr_event = (XRRNotifyEvent*)xev; + if(mgl_on_rr_notify(context, x11_context, xev, rr_event->subtype, event)) { + self->num_monitors = x11_context->num_monitors; + return; + } + + event->type = MGL_EVENT_UNKNOWN; + return; + } + } + switch(xev->type) { case KeyPress: { - x11_context *x11_context = self->context; - if(!self->key_repeat_enabled && xev->xkey.keycode == x11_context->prev_keycode_pressed && !x11_context->key_was_released) { event->type = MGL_EVENT_UNKNOWN; return; @@ -652,17 +989,16 @@ static void mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event x11_context->key_was_released = false; event->type = MGL_EVENT_KEY_PRESSED; - mgl_window_handle_key_event(self, &xev->xkey, event, context, true); + mgl_window_handle_key_event(self, &xev->xkey, event, context); mgl_window_handle_text_event(self, xev); return; } case KeyRelease: { - x11_context *x11_context = self->context; if(xev->xkey.keycode == x11_context->prev_keycode_pressed) x11_context->key_was_released = true; event->type = MGL_EVENT_KEY_RELEASED; - mgl_window_handle_key_event(self, &xev->xkey, event, context, false); + mgl_window_handle_key_event(self, &xev->xkey, event, context); return; } case ButtonPress: { @@ -694,7 +1030,6 @@ static void mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event return; } case FocusIn: { - x11_context *x11_context = self->context; XSetICFocus(x11_context->xic); XWMHints* hints = XGetWMHints(context->connection, self->window); @@ -709,7 +1044,6 @@ static void mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event return; } case FocusOut: { - x11_context *x11_context = self->context; XUnsetICFocus(x11_context->xic); event->type = MGL_EVENT_LOST_FOCUS; self->focused = false; @@ -717,6 +1051,10 @@ static void mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event } case ConfigureNotify: { while(XCheckTypedWindowEvent(context->connection, self->window, ConfigureNotify, xev)) {} + if(xev->xconfigure.x != self->pos.x || xev->xconfigure.y != self->pos.y) { + mgl_window_on_move(self, xev->xconfigure.x, xev->xconfigure.y); + } + if(xev->xconfigure.width != self->size.x || xev->xconfigure.height != self->size.y) { mgl_window_on_resize(self, xev->xconfigure.width, xev->xconfigure.height); @@ -725,6 +1063,7 @@ static void mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event event->size.height = self->size.y; return; } + event->type = MGL_EVENT_UNKNOWN; return; } @@ -743,8 +1082,6 @@ static void mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event return; } case SelectionRequest: { - x11_context *x11_context = self->context; - XSelectionEvent selection_event; selection_event.type = SelectionNotify; selection_event.requestor = xev->xselectionrequest.requestor; @@ -840,7 +1177,7 @@ bool mgl_window_poll_event(mgl_window *self, mgl_event *event) { if(XPending(display)) { XEvent xev; /* TODO: Move to window struct */ XNextEvent(display, &xev); - if(xev.xany.window == self->window || xev.type == ClientMessage) + if(xev.xany.window == self->window || xev.type == ClientMessage || xev.type - context->randr_event_base >= 0) mgl_window_on_receive_event(self, &xev, event, context); else event->type = MGL_EVENT_UNKNOWN; @@ -853,12 +1190,19 @@ bool mgl_window_poll_event(mgl_window *self, mgl_event *event) { void mgl_window_display(mgl_window *self) { mgl_context *context = mgl_get_context(); context->gl.glXSwapBuffers(context->connection, self->window); + context->gl.glFlush(); + context->gl.glFinish(); if(self->frame_time_limit > 0.000001) { double time_left_to_sleep = self->frame_time_limit - mgl_clock_get_elapsed_time_seconds(&self->frame_timer); if(time_left_to_sleep > 0.000001) usleep(time_left_to_sleep * 1000000.0); mgl_clock_restart(&self->frame_timer); + } else if(self->frame_time_limit_monitor >= 0.000001) { + double time_left_to_sleep = self->frame_time_limit_monitor - mgl_clock_get_elapsed_time_seconds(&self->frame_timer); + if(time_left_to_sleep > 0.000001) + usleep(time_left_to_sleep * 1000000.0); + mgl_clock_restart(&self->frame_timer); } } @@ -972,7 +1316,7 @@ void mgl_window_set_framerate_limit(mgl_window *self, int fps) { if(fps <= 0) self->frame_time_limit = 0.0; else - self->frame_time_limit = 1.0 / fps; + self->frame_time_limit = 1.0 / (double)fps; } void mgl_window_set_vsync_enabled(mgl_window *self, bool enabled) { -- cgit v1.2.3