From 98a6cfd3953b45eba7757f0836d6f825ba5b4819 Mon Sep 17 00:00:00 2001 From: Kenneth Graunke Date: Tue, 17 Jan 2023 15:48:20 -0800 Subject: [PATCH] loader: Add infrastructure for tracking active CRTC resources This provides a cached view of the current screen resources, with the coordinates and refresh rate for every active CRTC. It's currently only implemented for X11/XCB. Fixes: 47526556494 ("egl/x11: implement ANGLE_sync_control_rate") Reviewed-by: Adam Jackson Part-of: (cherry picked from commit 3170b63314f14f0031cb95bd5ee3a4726f26b43b) --- .pick_status.json | 2 +- src/loader/loader_dri3_helper.c | 184 ++++++++++++++++++++++++++++++-- src/loader/loader_dri3_helper.h | 36 +++++++ src/loader/meson.build | 2 +- 4 files changed, 213 insertions(+), 11 deletions(-) diff --git a/.pick_status.json b/.pick_status.json index 92c799d2a68..a7833ab73eb 100644 --- a/.pick_status.json +++ b/.pick_status.json @@ -184,7 +184,7 @@ "description": "loader: Add infrastructure for tracking active CRTC resources", "nominated": true, "nomination_type": 1, - "resolution": 0, + "resolution": 1, "main_sha": null, "because_sha": "47526556494f18cd2c02f978bccac7e2ba73adcd" }, diff --git a/src/loader/loader_dri3_helper.c b/src/loader/loader_dri3_helper.c index 5c724c1c7ef..adb21e4a5b6 100644 --- a/src/loader/loader_dri3_helper.c +++ b/src/loader/loader_dri3_helper.c @@ -357,15 +357,6 @@ loader_dri3_drawable_fini(struct loader_dri3_drawable *draw) for (i = 0; i < ARRAY_SIZE(draw->buffers); i++) dri3_free_render_buffer(draw, i); - if (draw->special_event) { - xcb_void_cookie_t cookie = - xcb_present_select_input_checked(draw->conn, draw->eid, draw->drawable, - XCB_PRESENT_EVENT_MASK_NO_EVENT); - - xcb_discard_reply(draw->conn, cookie.sequence); - xcb_unregister_for_special_event(draw->conn, draw->special_event); - } - if (draw->region) xcb_xfixes_destroy_region(draw->conn, draw->region); @@ -2350,6 +2341,181 @@ loader_dri3_update_drawable_geometry(struct loader_dri3_drawable *draw) } } +void +loader_dri3_init_screen_resources(struct loader_dri3_screen_resources *res, + xcb_connection_t *conn, + xcb_screen_t *screen) +{ + res->conn = conn; + res->screen = screen; + res->crtcs = NULL; + + mtx_init(&res->mtx, mtx_plain); +} + +void +loader_dri3_destroy_screen_resources(struct loader_dri3_screen_resources *res) +{ + mtx_destroy(&res->mtx); +} + +static unsigned +gcd_u32(unsigned a, unsigned b) +{ + assert(a > 0 || b > 0); + + while (b != 0) { + unsigned remainder = a % b; + a = b; + b = remainder; + } + + return a; +} + +static void +calculate_refresh_rate(const xcb_randr_mode_info_t *mode, + unsigned *numerator, unsigned *denominator) +{ + unsigned vtotal = mode->vtotal; + + /* Double-scan doubles the number of lines */ + if (mode->mode_flags & XCB_RANDR_MODE_FLAG_DOUBLE_SCAN) + vtotal *= 2; + + /* Interlace splits the frame into two fields; typically the monitor + * reports field rate. + */ + if (mode->mode_flags & XCB_RANDR_MODE_FLAG_INTERLACE) + vtotal /= 2; + + uint32_t dots = mode->htotal * vtotal; + + if (dots == 0) { + *numerator = 0; + *denominator = 1; + } else { + uint32_t gcd = gcd_u32(mode->dot_clock, dots); + + *numerator = mode->dot_clock / gcd; + *denominator = dots / gcd; + } +} + +bool +loader_dri3_update_screen_resources(struct loader_dri3_screen_resources *res) +{ + xcb_randr_get_crtc_info_cookie_t *crtc_cookies; + + /* If we have cached screen resources information, check each CRTC to + * see if it's up to date. Ideally, we'd watch PresentConfigureNotify + * events on the root window to see if something changed, but those only + * fire if the geometry changes. It misses CRTC changes which only + * alter the refresh rate. We also can't watch RandR events internally + * because they aren't XGE events. So, we just check every CRTC for now. + */ + bool config_unchanged = res->crtcs != NULL; + + crtc_cookies = malloc(res->num_crtcs * sizeof(*crtc_cookies)); + + for (unsigned c = 0; c < res->num_crtcs; c++) { + crtc_cookies[c] = + xcb_randr_get_crtc_info_unchecked(res->conn, res->crtcs[c].id, + res->config_timestamp); + } + + for (unsigned c = 0; c < res->num_crtcs; c++) { + xcb_randr_get_crtc_info_reply_t *reply = + xcb_randr_get_crtc_info_reply(res->conn, crtc_cookies[c], NULL); + + /* Although randrproto 1.4.0 says that RRGetCrtcInfo is supposed to + * return InvalidConfigTime if config_timestamp is out of date, the + * implementation in xserver as of 21.x doesn't actually do so. To + * detect changes in refresh rate, we check the returned timestamp + * on each tracked CRTC. + */ + if (!reply || + reply->status == XCB_RANDR_SET_CONFIG_INVALID_CONFIG_TIME || + reply->timestamp != res->crtcs[c].timestamp) { + config_unchanged = false; + /* continue to consume all replies */ + } + + free(reply); + } + + free(crtc_cookies); + + if (config_unchanged) + return false; + + /* Do RRGetScreenResourcesCurrent to query the list of CRTCs and modes, + * then RRGetCrtcInfo on each CRTC to determine what mode each uses, and + * use the mode to calculate the refresh rate. + */ + mtx_lock(&res->mtx); + + xcb_randr_get_screen_resources_current_cookie_t cookie = + xcb_randr_get_screen_resources_current_unchecked(res->conn, + res->screen->root); + xcb_randr_get_screen_resources_current_reply_t *reply = + xcb_randr_get_screen_resources_current_reply(res->conn, cookie, NULL); + + xcb_randr_crtc_t *new_crtcs = + xcb_randr_get_screen_resources_current_crtcs(reply); + + xcb_randr_mode_info_t *new_modes = + xcb_randr_get_screen_resources_current_modes(reply); + + res->config_timestamp = reply->config_timestamp; + + free(res->crtcs); + res->crtcs = calloc(reply->num_crtcs, sizeof(*res->crtcs)); + + crtc_cookies = malloc(reply->num_crtcs * sizeof(*crtc_cookies)); + + for (unsigned c = 0; c < reply->num_crtcs; c++) { + crtc_cookies[c] = + xcb_randr_get_crtc_info_unchecked(res->conn, new_crtcs[c], + res->config_timestamp); + } + + unsigned i = 0; + for (unsigned c = 0; c < reply->num_crtcs; c++) { + xcb_randr_get_crtc_info_reply_t *crtc_info = + xcb_randr_get_crtc_info_reply(res->conn, crtc_cookies[c], NULL); + + if (!crtc_info || crtc_info->mode == XCB_NONE) + continue; + + res->crtcs[i].id = new_crtcs[c]; + res->crtcs[i].timestamp = crtc_info->timestamp; + res->crtcs[i].x = crtc_info->x; + res->crtcs[i].y = crtc_info->y; + res->crtcs[i].width = crtc_info->width; + res->crtcs[i].height = crtc_info->height; + + for (int m = 0; m < reply->num_modes; m++) { + if (new_modes[m].id == crtc_info->mode) { + calculate_refresh_rate(&new_modes[m], + &res->crtcs[i].refresh_numerator, + &res->crtcs[i].refresh_denominator); + break; + } + } + + i++; + free(crtc_info); + } + + res->num_crtcs = i; + + free(crtc_cookies); + free(reply); + + mtx_unlock(&res->mtx); + return true; +} /** * Make sure the server has flushed all pending swap buffers to hardware diff --git a/src/loader/loader_dri3_helper.h b/src/loader/loader_dri3_helper.h index 5eb9c90a658..a828aa43248 100644 --- a/src/loader/loader_dri3_helper.h +++ b/src/loader/loader_dri3_helper.h @@ -294,4 +294,40 @@ loader_dri3_swapbuffer_barrier(struct loader_dri3_drawable *draw); void loader_dri3_close_screen(__DRIscreen *dri_screen); + +struct loader_dri3_crtc_info { + xcb_randr_crtc_t id; + xcb_timestamp_t timestamp; + + int16_t x, y; + uint16_t width, height; + + unsigned refresh_numerator; + unsigned refresh_denominator; +}; + +struct loader_dri3_screen_resources { + mtx_t mtx; + + xcb_connection_t *conn; + xcb_screen_t *screen; + + xcb_timestamp_t config_timestamp; + + /* Number of CRTCs with an active mode set */ + unsigned num_crtcs; + struct loader_dri3_crtc_info *crtcs; +}; + +void +loader_dri3_init_screen_resources(struct loader_dri3_screen_resources *res, + xcb_connection_t *conn, + xcb_screen_t *screen); +bool +loader_dri3_update_screen_resources(struct loader_dri3_screen_resources *res); + +void +loader_dri3_destroy_screen_resources(struct loader_dri3_screen_resources *res); + + #endif diff --git a/src/loader/meson.build b/src/loader/meson.build index bdebcb725a4..b03b229cd85 100644 --- a/src/loader/meson.build +++ b/src/loader/meson.build @@ -28,7 +28,7 @@ if with_platform_x11 and with_dri3 include_directories : [inc_include, inc_src], dependencies : [ dep_libdrm, dep_xcb_dri3, dep_xcb_present, dep_xcb_sync, dep_xshmfence, - dep_xcb_xfixes, + dep_xcb_xfixes, dep_xcb_xrandr, ], build_by_default : false, )