[cogl/wip/wayland: 4/8] kms: Add mirror support and env var configurability



commit 754bd906328f380cd19df692e4806915579f274a
Author: Robert Bragg <robert linux intel com>
Date:   Wed Jan 25 19:29:29 2012 +0000

    kms: Add mirror support and env var configurability
    
    This adds support for mirroring the display output on two KMS
    connectors.
    
    This patch also checks for a number of environment variables that can
    influence how KMS is configured. The following variables can be set:
    
    COGL_KMS_MIRROR: If this is set to anything then Cogl will try and setup
    two connectors with the same resolution so that onscreen frame buffers
    can be mirrored.
    
    COGL_KMS_CONNECTOR0: This can be set to an integer identifier for a
    specific KMS connector id to use for the first output.
    
    COGL_KMS_CONNECTOR0_MODE: Can be set to a mode name like "1024x768"
    explicitly select what mode should be used for the first output.
    
    If COGL_KMS_MIRROR is set then COGL_KMS_CONNECTOR1 and
    COGL_KMS_CONNECTOR1_MODE can optionally be set to specify a connector id
    and mode name for the second output.

 cogl/cogl-display.c               |    2 +-
 cogl/cogl-renderer.c              |    4 +-
 cogl/winsys/cogl-winsys-egl-kms.c |  474 ++++++++++++++++++++++++++++++-------
 3 files changed, 397 insertions(+), 83 deletions(-)
---
diff --git a/cogl/cogl-display.c b/cogl/cogl-display.c
index 5c7f588..591c3c5 100644
--- a/cogl/cogl-display.c
+++ b/cogl/cogl-display.c
@@ -101,7 +101,7 @@ cogl_display_new (CoglRenderer *renderer,
     {
       g_warning ("Failed to connect renderer: %s\n", error->message);
       g_error_free (error);
-      g_object_unref (display->renderer);
+      cogl_object_unref (display->renderer);
       g_slice_free (CoglDisplay, display);
       return NULL;
     }
diff --git a/cogl/cogl-renderer.c b/cogl/cogl-renderer.c
index 694ea59..4e91e77 100644
--- a/cogl/cogl-renderer.c
+++ b/cogl/cogl-renderer.c
@@ -144,7 +144,9 @@ static void
 _cogl_renderer_free (CoglRenderer *renderer)
 {
   const CoglWinsysVtable *winsys = _cogl_renderer_get_winsys (renderer);
-  winsys->renderer_disconnect (renderer);
+
+  if (winsys)
+    winsys->renderer_disconnect (renderer);
 
 #ifndef HAVE_DIRECTLY_LINKED_GL_LIBRARY
   if (renderer->libgl_module)
diff --git a/cogl/winsys/cogl-winsys-egl-kms.c b/cogl/winsys/cogl-winsys-egl-kms.c
index 10ef999..825cfbc 100644
--- a/cogl/winsys/cogl-winsys-egl-kms.c
+++ b/cogl/winsys/cogl-winsys-egl-kms.c
@@ -42,6 +42,7 @@
 #include <sys/fcntl.h>
 #include <unistd.h>
 #include <string.h>
+#include <stdlib.h>
 
 #include "cogl-winsys-egl-kms-private.h"
 #include "cogl-winsys-egl-private.h"
@@ -60,16 +61,29 @@ typedef struct _CoglRendererKMS
   CoglPollFD poll_fd;
 } CoglRendererKMS;
 
-typedef struct _CoglDisplayKMS
+typedef struct _CoglOutputKMS
 {
   drmModeConnector *connector;
   drmModeEncoder *encoder;
+  drmModeCrtc *saved_crtc;
+  drmModeModeInfo *modes;
+  int n_modes;
   drmModeModeInfo mode;
-  drmModeCrtcPtr saved_crtc;
+} CoglOutputKMS;
+
+typedef struct _CoglDisplayKMS
+{
+  GList *outputs;
   int width, height;
   gboolean pending_swap_notify;
 } CoglDisplayKMS;
 
+typedef struct _CoglFlipKMS
+{
+  CoglOnscreen *onscreen;
+  int pending;
+} CoglFlipKMS;
+
 typedef struct _CoglOnscreenKMS
 {
   struct gbm_surface *surface;
@@ -157,6 +171,210 @@ close_fd:
 }
 
 static gboolean
+is_connector_excluded (int id,
+                       int *excluded_connectors,
+                       int n_excluded_connectors)
+{
+  int i;
+  for (i = 0; i < n_excluded_connectors; i++)
+    if (excluded_connectors[i] == id)
+      return TRUE;
+  return FALSE;
+}
+
+static drmModeConnector *
+find_connector (int fd,
+                drmModeRes *resources,
+                int *excluded_connectors,
+                int n_excluded_connectors)
+{
+  int i;
+
+  for (i = 0; i < resources->count_connectors; i++)
+    {
+      drmModeConnector *connector =
+        drmModeGetConnector (fd, resources->connectors[i]);
+
+      if (connector &&
+          connector->connection == DRM_MODE_CONNECTED &&
+          connector->count_modes > 0 &&
+          !is_connector_excluded (connector->connector_id,
+                                  excluded_connectors,
+                                  n_excluded_connectors))
+        return connector;
+      drmModeFreeConnector(connector);
+    }
+  return NULL;
+}
+
+static gboolean
+find_mirror_modes (drmModeModeInfo *modes0,
+                   int n_modes0,
+                   drmModeModeInfo *modes1,
+                   int n_modes1,
+                   drmModeModeInfo *mode1_out,
+                   drmModeModeInfo *mode0_out)
+{
+  int i;
+  for (i = 0; i < n_modes0; i++)
+    {
+      int j;
+      drmModeModeInfo *mode0 = &modes0[i];
+      for (j = 0; j < n_modes1; j++)
+        {
+          drmModeModeInfo *mode1 = &modes1[j];
+          if (mode1->hdisplay == mode0->hdisplay &&
+              mode1->vdisplay == mode0->vdisplay)
+            {
+              *mode0_out = *mode0;
+              *mode1_out = *mode1;
+              return TRUE;
+            }
+        }
+    }
+  return FALSE;
+}
+
+static drmModeModeInfo builtin_1024x768 =
+{
+	63500,			/* clock */
+	1024, 1072, 1176, 1328, 0,
+	768, 771, 775, 798, 0,
+	59920,
+	DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC,
+	0,
+	"1024x768"
+};
+
+static gboolean
+is_panel (int type)
+{
+  return (type == DRM_MODE_CONNECTOR_LVDS ||
+          type == DRM_MODE_CONNECTOR_eDP);
+}
+
+static CoglOutputKMS *
+find_output (int _index,
+             int fd,
+             drmModeRes *resources,
+             int *excluded_connectors,
+             int n_excluded_connectors,
+             GError **error)
+{
+  char *connector_env_name = g_strdup_printf ("COGL_KMS_CONNECTOR%d", _index);
+  char *mode_env_name;
+  drmModeConnector *connector;
+  drmModeEncoder *encoder;
+  CoglOutputKMS *output;
+  drmModeModeInfo *modes;
+  int n_modes;
+
+  if (getenv (connector_env_name))
+    {
+      unsigned long id = strtoul (getenv (connector_env_name), NULL, 10);
+      connector = drmModeGetConnector (fd, id);
+    }
+  else
+    connector = NULL;
+  g_free (connector_env_name);
+
+  if (connector == NULL)
+    connector = find_connector (fd, resources,
+                                excluded_connectors, n_excluded_connectors);
+  if (connector == NULL)
+    {
+      g_set_error (error, COGL_WINSYS_ERROR,
+                   COGL_WINSYS_ERROR_INIT,
+                   "No currently active connector found");
+      return NULL;
+    }
+
+  /* XXX: At this point it seems connector->encoder_id may be an invalid id of 0
+   * even though the connector is marked as connected. Referencing ->encoders[0]
+   * seems more reliable. */
+  encoder = drmModeGetEncoder (fd, connector->encoders[0]);
+
+  output = g_slice_new0 (CoglOutputKMS);
+  output->connector = connector;
+  output->encoder = encoder;
+  output->saved_crtc = drmModeGetCrtc (fd, encoder->crtc_id);
+
+  if (is_panel (connector->connector_type))
+    {
+      n_modes = connector->count_modes + 1;
+      modes = g_new (drmModeModeInfo, n_modes);
+      memcpy (modes, connector->modes,
+              sizeof (drmModeModeInfo) * connector->count_modes);
+      /* TODO: parse EDID */
+      modes[n_modes - 1] = builtin_1024x768;
+    }
+  else
+    {
+      n_modes = connector->count_modes;
+      modes = g_new (drmModeModeInfo, n_modes);
+      memcpy (modes, connector->modes,
+              sizeof (drmModeModeInfo) * n_modes);
+    }
+
+  mode_env_name = g_strdup_printf ("COGL_KMS_CONNECTOR%d_MODE", _index);
+  if (getenv (mode_env_name))
+    {
+      const char *name = getenv (mode_env_name);
+      int i;
+      gboolean found = FALSE;
+      drmModeModeInfo mode;
+
+      for (i = 0; i < n_modes; i++)
+        {
+          if (strcmp (modes[i].name, name) == 0)
+            {
+              found = TRUE;
+              break;
+            }
+        }
+      if (!found)
+        {
+          g_free (mode_env_name);
+          g_set_error (error, COGL_WINSYS_ERROR,
+                       COGL_WINSYS_ERROR_INIT,
+                       "COGL_KMS_CONNECTOR%d_MODE of %s could not be found",
+                       _index, name);
+          return NULL;
+        }
+      n_modes = 1;
+      mode = modes[i];
+      g_free (modes);
+      modes = g_new (drmModeModeInfo, 1);
+      modes[0] = mode;
+    }
+  g_free (mode_env_name);
+
+  output->modes = modes;
+  output->n_modes = n_modes;
+
+  return output;
+}
+
+static gboolean
+set_crtc (int fd, uint32_t fb_id, CoglOutputKMS *output, GError **error)
+{
+  int ret = drmModeSetCrtc (fd,
+                            output->encoder->crtc_id,
+                            fb_id, 0, 0,
+                            &output->connector->connector_id, 1,
+                            &output->mode);
+  if (ret)
+    {
+      g_set_error (error, COGL_WINSYS_ERROR,
+                   COGL_WINSYS_ERROR_INIT,
+                   "Failed to set mode %s: %m", output->mode.name);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
 _cogl_winsys_egl_display_setup (CoglDisplay *display,
                                 GError **error)
 {
@@ -167,9 +385,12 @@ _cogl_winsys_egl_display_setup (CoglDisplay *display,
   CoglEGLWinsysFeature surfaceless_feature = 0;
   const char *surfaceless_feature_name = "";
   drmModeRes *resources;
-  drmModeConnector *connector;
-  drmModeEncoder *encoder;
-  int i;
+  CoglOutputKMS *output0, *output1;
+  gboolean mirror;
+  struct gbm_bo *bo;
+  uint32_t handle, pitch, fb_id;
+  int width;
+  int height;
 
   kms_display = g_slice_new0 (CoglDisplayKMS);
   egl_display->platform = kms_display;
@@ -208,57 +429,127 @@ _cogl_winsys_egl_display_setup (CoglDisplay *display,
       return FALSE;
     }
 
-  for (i = 0; i < resources->count_connectors; i++)
-    {
-      connector = drmModeGetConnector (kms_renderer->fd,
-                                       resources->connectors[i]);
-      if (connector == NULL)
-        continue;
-
-      if (connector->connection == DRM_MODE_CONNECTED &&
-          connector->count_modes > 0)
-        break;
+  output0 = find_output (0,
+                         kms_renderer->fd,
+                         resources,
+                         NULL,
+                         0, /* n excluded connectors */
+                         error);
+  kms_display->outputs = g_list_append (kms_display->outputs, output0);
+  if (!output0)
+    return FALSE;
+
+  if (getenv ("COGL_KMS_MIRROR"))
+    mirror = TRUE;
+  else
+    mirror = FALSE;
 
-      drmModeFreeConnector(connector);
+  if (mirror)
+    {
+      int exclude_connector = output0->connector->connector_id;
+      output1 = find_output (1,
+                             kms_renderer->fd,
+                             resources,
+                             &exclude_connector,
+                             1, /* n excluded connectors */
+                             error);
+      if (!output1)
+        return FALSE;
+
+      kms_display->outputs = g_list_append (kms_display->outputs, output1);
+
+      if (!find_mirror_modes (output0->modes, output0->n_modes,
+                              output1->modes, output1->n_modes,
+                              &output0->mode,
+                              &output1->mode))
+        {
+          g_set_error (error, COGL_WINSYS_ERROR,
+                       COGL_WINSYS_ERROR_INIT,
+                       "Failed to find matching modes for mirroring");
+          return FALSE;
+        }
     }
+  else
+    output0->mode = output0->modes[0];
+
+  width = output0->mode.hdisplay;
+  height = output0->mode.vdisplay;
 
-  if (i == resources->count_connectors)
+  bo = gbm_bo_create (kms_renderer->gbm,
+                      width, height, GBM_BO_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT);
+  pitch = gbm_bo_get_pitch (bo);
+  handle = gbm_bo_get_handle (bo).u32;
+  if (drmModeAddFB (kms_renderer->fd,
+                    width,
+                    height,
+                    24, /* depth */
+                    32, /* bpp */
+                    pitch,
+                    handle,
+                    &fb_id) != 0)
     {
       g_set_error (error, COGL_WINSYS_ERROR,
                    COGL_WINSYS_ERROR_INIT,
-                   "No currently active connector found");
+                   "Failed to create initial framebuffer");
       return FALSE;
     }
 
-  for (i = 0; i < resources->count_encoders; i++)
-    {
-      encoder = drmModeGetEncoder (kms_renderer->fd, resources->encoders[i]);
+  if (!set_crtc (kms_renderer->fd, fb_id, output0, error))
+    return FALSE;
 
-      if (encoder == NULL)
-        continue;
+  if (mirror)
+    if (!set_crtc (kms_renderer->fd, fb_id, output1, error))
+      return FALSE;
 
-      if (encoder->encoder_id == connector->encoder_id)
-        break;
+  kms_display->width = width;
+  kms_display->height = height;
 
-      drmModeFreeEncoder (encoder);
-    }
+  return TRUE;
+}
 
-  kms_display->saved_crtc = drmModeGetCrtc (kms_renderer->fd,
-                                            encoder->crtc_id);
+static void
+output_free (int fd, CoglOutputKMS *output)
+{
+  if (output->modes)
+    g_free (output->modes);
 
-  kms_display->connector = connector;
-  kms_display->encoder = encoder;
-  kms_display->mode = connector->modes[0];
-  kms_display->width = kms_display->mode.hdisplay;
-  kms_display->height = kms_display->mode.vdisplay;
+  if (output->encoder)
+    drmModeFreeEncoder (output->encoder);
 
-  return TRUE;
+  if (output->connector)
+    {
+      if (output->saved_crtc)
+        {
+          int ret = drmModeSetCrtc (fd,
+                                    output->saved_crtc->crtc_id,
+                                    output->saved_crtc->buffer_id,
+                                    output->saved_crtc->x,
+                                    output->saved_crtc->y,
+                                    &output->connector->connector_id, 1,
+                                    &output->saved_crtc->mode);
+          if (ret)
+            g_warning (G_STRLOC ": Error restoring saved CRTC");
+        }
+      drmModeFreeConnector (output->connector);
+    }
+
+  g_slice_free (CoglOutputKMS, output);
 }
 
 static void
 _cogl_winsys_egl_display_destroy (CoglDisplay *display)
 {
   CoglDisplayEGL *egl_display = display->winsys;
+  CoglDisplayKMS *kms_display = egl_display->platform;
+  CoglRenderer *renderer = display->renderer;
+  CoglRendererEGL *egl_renderer = renderer->winsys;
+  CoglRendererKMS *kms_renderer = egl_renderer->platform;
+  GList *l;
+
+  for (l = kms_display->outputs; l; l = l->next)
+    output_free (kms_renderer->fd, l->data);
+  g_list_free (kms_display->outputs);
+  kms_display->outputs = NULL;
 
   g_slice_free (CoglDisplayKMS, egl_display->platform);
 }
@@ -288,27 +579,6 @@ _cogl_winsys_egl_context_created (CoglDisplay *display,
 static void
 _cogl_winsys_egl_cleanup_context (CoglDisplay *display)
 {
-  CoglDisplayEGL *egl_display = display->winsys;
-  CoglDisplayKMS *kms_display = egl_display->platform;
-  CoglRenderer *renderer = display->renderer;
-  CoglRendererEGL *egl_renderer = renderer->winsys;
-  CoglRendererKMS *kms_renderer = egl_renderer->platform;
-
-  /* Restore the saved CRTC - this failing should not propagate an error */
-  if (kms_display->saved_crtc)
-    {
-      int ret = drmModeSetCrtc (kms_renderer->fd,
-                                kms_display->saved_crtc->crtc_id,
-                                kms_display->saved_crtc->buffer_id,
-                                kms_display->saved_crtc->x,
-                                kms_display->saved_crtc->y,
-                                &kms_display->connector->connector_id, 1,
-                                &kms_display->saved_crtc->mode);
-      if (ret)
-        g_critical (G_STRLOC ": Error restoring saved CRTC");
-
-      drmModeFreeCrtc (kms_display->saved_crtc);
-    }
 }
 
 static void
@@ -342,7 +612,8 @@ page_flip_handler (int fd,
                    unsigned int usec,
                    void *data)
 {
-  CoglOnscreen *onscreen = data;
+  CoglFlipKMS *flip = data;
+  CoglOnscreen *onscreen = flip->onscreen;
   CoglOnscreenEGL *egl_onscreen = onscreen->winsys;
   CoglOnscreenKMS *kms_onscreen = egl_onscreen->platform;
   CoglContext *context = COGL_FRAMEBUFFER (onscreen)->context;
@@ -350,19 +621,29 @@ page_flip_handler (int fd,
   CoglDisplayEGL *egl_display = display->winsys;
   CoglDisplayKMS *kms_display = egl_display->platform;
 
-  free_current_bo (onscreen);
+  /* We're only ready to dispatch a swap notification once all outputs
+   * have flipped... */
+  flip->pending--;
+  if (flip->pending == 0)
+    {
+      /* We only want to notify that the swap is complete when the application
+       * calls cogl_context_dispatch so instead of immediately notifying we'll
+       * set a flag to remember to notify later */
+      kms_display->pending_swap_notify = TRUE;
+      kms_onscreen->pending_swap_notify = TRUE;
+
+      free_current_bo (onscreen);
 
-  kms_onscreen->current_fb_id = kms_onscreen->next_fb_id;
-  kms_onscreen->next_fb_id = 0;
+      kms_onscreen->current_fb_id = kms_onscreen->next_fb_id;
+      kms_onscreen->next_fb_id = 0;
+
+      kms_onscreen->current_bo = kms_onscreen->next_bo;
+      kms_onscreen->next_bo = NULL;
 
-  kms_onscreen->current_bo = kms_onscreen->next_bo;
-  kms_onscreen->next_bo = NULL;
+      cogl_object_unref (flip->onscreen);
 
-  /* We only want to notify that the swap is complete when the
-     application calls cogl_context_dispatch so instead of immediately
-     notifying we'll set a flag to remember to notify later */
-  kms_display->pending_swap_notify = TRUE;
-  kms_onscreen->pending_swap_notify = TRUE;
+      g_slice_free (CoglFlipKMS, flip);
+    }
 }
 
 static void
@@ -387,7 +668,9 @@ _cogl_winsys_onscreen_swap_buffers (CoglOnscreen *onscreen)
   CoglRendererKMS *kms_renderer = egl_renderer->platform;
   CoglOnscreenEGL *egl_onscreen = onscreen->winsys;
   CoglOnscreenKMS *kms_onscreen = egl_onscreen->platform;
-  EGLint handle, pitch;
+  uint32_t handle, pitch;
+  CoglFlipKMS *flip;
+  GList *l;
 
   /* If we already have a pending swap then block until it completes */
   while (kms_onscreen->next_fb_id != 0)
@@ -409,20 +692,50 @@ _cogl_winsys_onscreen_swap_buffers (CoglOnscreen *onscreen)
                     32, /* bpp */
                     pitch,
                     handle,
-                    &kms_onscreen->next_fb_id) == 0)
+                    &kms_onscreen->next_fb_id))
     {
-      drmModePageFlip (kms_renderer->fd,
-                       kms_display->encoder->crtc_id,
-                       kms_onscreen->next_fb_id,
-                       DRM_MODE_PAGE_FLIP_EVENT,
-                       onscreen);
+      g_warning ("Failed to create new back buffer handle: %m");
+      gbm_surface_release_bo (kms_onscreen->surface,
+                              kms_onscreen->next_bo);
+      kms_onscreen->next_bo = NULL;
+      kms_onscreen->next_fb_id = 0;
+      return;
     }
-  else
+
+  flip = g_slice_new0 (CoglFlipKMS);
+  flip->onscreen = onscreen;
+
+  for (l = kms_display->outputs; l; l = l->next)
     {
+      CoglOutputKMS *output = l->data;
+
+      if (drmModePageFlip (kms_renderer->fd,
+                           output->encoder->crtc_id,
+                           kms_onscreen->next_fb_id,
+                           DRM_MODE_PAGE_FLIP_EVENT,
+                           flip))
+        {
+          g_warning ("Failed to flip: %m");
+          continue;
+        }
+
+      flip->pending++;
+    }
+
+  if (flip->pending == 0)
+    {
+      drmModeRmFB (kms_renderer->fd, kms_onscreen->next_fb_id);
       gbm_surface_release_bo (kms_onscreen->surface,
                               kms_onscreen->next_bo);
       kms_onscreen->next_bo = NULL;
       kms_onscreen->next_fb_id = 0;
+      g_slice_free (CoglFlipKMS, flip);
+      flip = NULL;
+    }
+  else
+    {
+      /* Ensure the onscreen remains valid while it has any pending flips... */
+      cogl_object_ref (flip->onscreen);
     }
 }
 
@@ -464,8 +777,8 @@ _cogl_winsys_onscreen_init (CoglOnscreen *onscreen,
 
   kms_onscreen->surface =
     gbm_surface_create (kms_renderer->gbm,
-                        kms_display->mode.hdisplay,
-                        kms_display->mode.vdisplay,
+                        kms_display->width,
+                        kms_display->height,
                         GBM_BO_FORMAT_XRGB8888);
   if (!kms_onscreen->surface)
     {
@@ -502,7 +815,6 @@ _cogl_winsys_onscreen_deinit (CoglOnscreen *onscreen)
   CoglContext *context = framebuffer->context;
   CoglRenderer *renderer = context->display->renderer;
   CoglRendererEGL *egl_renderer = renderer->winsys;
-  CoglRendererKMS *kms_renderer = egl_renderer->platform;
   CoglOnscreenEGL *egl_onscreen = onscreen->winsys;
   CoglOnscreenKMS *kms_onscreen;
 
@@ -512,9 +824,9 @@ _cogl_winsys_onscreen_deinit (CoglOnscreen *onscreen)
 
   kms_onscreen = egl_onscreen->platform;
 
-  /* If have a pending swap then block until it completes */
-  while (kms_onscreen->next_fb_id != 0)
-    handle_drm_event (kms_renderer);
+  /* flip state takes a reference on the onscreen so there should
+   * never be outstanding flips when we reach here. */
+  g_return_if_fail (kms_onscreen->next_fb_id == 0);
 
   free_current_bo (onscreen);
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]