loader: Add infrastructure for tracking active CRTC resources
authorKenneth Graunke <kenneth@whitecape.org>
Tue, 17 Jan 2023 23:48:20 +0000 (15:48 -0800)
committerEric Engestrom <eric@engestrom.ch>
Wed, 8 Feb 2023 20:34:39 +0000 (20:34 +0000)
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 <ajax@redhat.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/20665>
(cherry picked from commit 3170b63314f14f0031cb95bd5ee3a4726f26b43b)

.pick_status.json
src/loader/loader_dri3_helper.c
src/loader/loader_dri3_helper.h
src/loader/meson.build

index 65981e2..8dda0b7 100644 (file)
         "description": "loader: Add infrastructure for tracking active CRTC resources",
         "nominated": true,
         "nomination_type": 1,
-        "resolution": 0,
+        "resolution": 1,
         "main_sha": null,
         "because_sha": "47526556494f18cd2c02f978bccac7e2ba73adcd"
     },
index f04241e..5a97ce3 100644 (file)
@@ -359,15 +359,6 @@ loader_dri3_drawable_fini(struct loader_dri3_drawable *draw)
          dri3_free_render_buffer(draw, draw->buffers[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);
 
@@ -2338,6 +2329,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
index 81f66a0..6d20162 100644 (file)
@@ -295,4 +295,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
index 6334cb9..e5a8cea 100644 (file)
@@ -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,
   )