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.

Reviewed-by: Neil Roberts <neil@linux.intel.com>
This commit is contained in:
Robert Bragg 2012-01-25 19:29:29 +00:00 committed by Neil Roberts
parent f9d3ea03ec
commit 125c31a70b
2 changed files with 398 additions and 84 deletions

View File

@ -142,7 +142,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)

View File

@ -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"
@ -61,16 +62,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:
return FALSE;
}
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)
@ -168,9 +386,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;
@ -211,57 +432,127 @@ _cogl_winsys_egl_display_setup (CoglDisplay *display,
return FALSE;
}
for (i = 0; i < resources->count_connectors; i++)
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;
if (mirror)
{
connector = drmModeGetConnector (kms_renderer->fd,
resources->connectors[i]);
if (connector == NULL)
continue;
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;
if (connector->connection == DRM_MODE_CONNECTED &&
connector->count_modes > 0)
break;
kms_display->outputs = g_list_append (kms_display->outputs, output1);
drmModeFreeConnector(connector);
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];
if (i == resources->count_connectors)
width = output0->mode.hdisplay;
height = output0->mode.vdisplay;
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;
drmModeFreeEncoder (encoder);
}
kms_display->saved_crtc = drmModeGetCrtc (kms_renderer->fd,
encoder->crtc_id);
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;
kms_display->width = width;
kms_display->height = height;
return TRUE;
}
static void
output_free (int fd, CoglOutputKMS *output)
{
if (output->modes)
g_free (output->modes);
if (output->encoder)
drmModeFreeEncoder (output->encoder);
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);
}
@ -291,27 +582,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
@ -345,7 +615,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;
@ -353,19 +624,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;
kms_onscreen->current_fb_id = kms_onscreen->next_fb_id;
kms_onscreen->next_fb_id = 0;
free_current_bo (onscreen);
kms_onscreen->current_bo = kms_onscreen->next_bo;
kms_onscreen->next_bo = NULL;
kms_onscreen->current_fb_id = kms_onscreen->next_fb_id;
kms_onscreen->next_fb_id = 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;
kms_onscreen->current_bo = kms_onscreen->next_bo;
kms_onscreen->next_bo = NULL;
cogl_object_unref (flip->onscreen);
g_slice_free (CoglFlipKMS, flip);
}
}
static void
@ -390,7 +671,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)
@ -412,20 +695,50 @@ _cogl_winsys_onscreen_swap_buffers (CoglOnscreen *onscreen)
32, /* bpp */
pitch,
handle,
&kms_onscreen->next_fb_id) == 0)
{
drmModePageFlip (kms_renderer->fd,
kms_display->encoder->crtc_id,
kms_onscreen->next_fb_id,
DRM_MODE_PAGE_FLIP_EVENT,
onscreen);
}
else
&kms_onscreen->next_fb_id))
{
g_warning ("Failed to create new back buffer handle: %m");
gbm_surface_release_buffer (kms_onscreen->surface,
kms_onscreen->next_bo);
kms_onscreen->next_bo = NULL;
kms_onscreen->next_fb_id = 0;
return;
}
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_buffer (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);
}
}
@ -467,8 +780,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,
GBM_BO_USE_SCANOUT |
GBM_BO_USE_RENDERING);
@ -508,7 +821,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;
@ -518,9 +830,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);