summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Bragg <robert@linux.intel.com>2012-01-25 19:29:29 +0000
committerRobert Bragg <robert@linux.intel.com>2012-01-27 22:11:51 +0000
commit0c12bb801e57650e56bd862f80f593f88a19fbd6 (patch)
treeddb4954d4d1c2408147b91c91d6914cb0604537b
parentb28c29b8b78843e4847fcf5c16be97a5d6172bfa (diff)
downloadcogl-0c12bb801e57650e56bd862f80f593f88a19fbd6.tar.gz
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.
-rw-r--r--cogl/cogl-display.c2
-rw-r--r--cogl/cogl-renderer.c4
-rw-r--r--cogl/winsys/cogl-winsys-egl-kms.c474
3 files changed, 397 insertions, 83 deletions
diff --git a/cogl/cogl-display.c b/cogl/cogl-display.c
index 5c7f588c..591c3c5d 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 c8af6c4c..f18daea0 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 a46104d0..b1aecd75 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);