summaryrefslogtreecommitdiff
path: root/cogl/winsys/cogl-winsys-egl-kms.c
diff options
context:
space:
mode:
Diffstat (limited to 'cogl/winsys/cogl-winsys-egl-kms.c')
-rw-r--r--cogl/winsys/cogl-winsys-egl-kms.c523
1 files changed, 479 insertions, 44 deletions
diff --git a/cogl/winsys/cogl-winsys-egl-kms.c b/cogl/winsys/cogl-winsys-egl-kms.c
index a81237de..260342ff 100644
--- a/cogl/winsys/cogl-winsys-egl-kms.c
+++ b/cogl/winsys/cogl-winsys-egl-kms.c
@@ -334,7 +334,6 @@ update_outputs (CoglRenderer *renderer)
{
CoglOutput *output = NULL;
CoglOutputKMS *kms_output;
- const char *type_name;
GList *l;
drmModeConnector *connector =
@@ -356,7 +355,7 @@ update_outputs (CoglRenderer *renderer)
}
/* If we already have a CoglOutput corresponding to this
- * connector id then we can simply keep it and move on... */
+ * connector id then we keep it... */
for (l = renderer->outputs; l; l = l->next)
{
CoglOutput *existing_output = l->data;
@@ -366,53 +365,82 @@ update_outputs (CoglRenderer *renderer)
{
renderer->outputs = g_list_delete_link (renderer->outputs, l);
output = existing_output;
+ kms_output = output->winsys;
+
+ if (output->pending != output->state)
+ {
+ g_warning ("Unexpected pending state associated with CoglOutput "
+ "%s while processing events. Pending output state "
+ "shouldn't be maintained between mainloop "
+ "iterations\n", output->state->name);
+ }
+
+ break;
}
}
- if (output)
+ if (!output)
{
- new_outputs = g_list_prepend (new_outputs, output);
- drmModeFreeConnector (connector);
- continue;
- }
+ const char *type_name;
- if (connector->connector_type < G_N_ELEMENTS (kms_connector_types))
- type_name = kms_connector_types[connector->connector_type];
- else
- type_name = kms_connector_types[0];
+ if (connector->connector_type < G_N_ELEMENTS (kms_connector_types))
+ type_name = kms_connector_types[connector->connector_type];
+ else
+ type_name = kms_connector_types[0];
- output = _cogl_output_new (type_name);
+ output = _cogl_output_new (type_name);
- kms_output = g_slice_new0 (CoglOutputKMS);
- kms_output->connector_id = connector->connector_id;
- kms_output->connector = connector;
+ kms_output = g_slice_new0 (CoglOutputKMS);
+ kms_output->connector_id = connector->connector_id;
+ kms_output->connector = connector;
+
+ _cogl_output_set_winsys_data (output,
+ kms_output,
+ kms_output_destroy_cb);
+ }
+
+ for (j = 0; j < connector->count_modes; j++)
+ {
+ drmModeModeInfo *info = &connector->modes[j];
+
+ CoglMode *mode = _cogl_mode_new (info->name);
+ mode->width = info->hdisplay;
+ mode->height = info->vdisplay;
+ mode->refresh_rate =
+ (info->clock / ((float)info->htotal * info->vtotal));
+ new_modes = g_list_prepend (new_modes, mode);
+ }
+
+ if (output->modes)
+ g_list_free_full (output->modes, cogl_object_unref);
+ output->modes = g_list_reverse (new_modes);
/* We can't determinine anything about the relative position
* of the outputs... */
- output->x = output->y = 0;
+ output->state->x = output->state->y = 0;
- output->mm_width = connector->mmWidth;
- output->mm_height = connector->mmHeight;
+ output->state->mm_width = connector->mmWidth;
+ output->state->mm_height = connector->mmHeight;
switch (connector->subpixel)
{
case DRM_MODE_SUBPIXEL_UNKNOWN:
- output->subpixel_order = COGL_SUBPIXEL_ORDER_UNKNOWN;
+ output->state->subpixel_order = COGL_SUBPIXEL_ORDER_UNKNOWN;
break;
case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB:
- output->subpixel_order = COGL_SUBPIXEL_ORDER_HORIZONTAL_RGB;
+ output->state->subpixel_order = COGL_SUBPIXEL_ORDER_HORIZONTAL_RGB;
break;
case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR:
- output->subpixel_order = COGL_SUBPIXEL_ORDER_HORIZONTAL_BGR;
+ output->state->subpixel_order = COGL_SUBPIXEL_ORDER_HORIZONTAL_BGR;
break;
case DRM_MODE_SUBPIXEL_VERTICAL_RGB:
- output->subpixel_order = COGL_SUBPIXEL_ORDER_VERTICAL_RGB;
+ output->state->subpixel_order = COGL_SUBPIXEL_ORDER_VERTICAL_RGB;
break;
case DRM_MODE_SUBPIXEL_VERTICAL_BGR:
- output->subpixel_order = COGL_SUBPIXEL_ORDER_VERTICAL_BGR;
+ output->state->subpixel_order = COGL_SUBPIXEL_ORDER_VERTICAL_BGR;
break;
case DRM_MODE_SUBPIXEL_NONE:
- output->subpixel_order = COGL_SUBPIXEL_ORDER_NONE;
+ output->state->subpixel_order = COGL_SUBPIXEL_ORDER_NONE;
break;
}
@@ -439,29 +467,19 @@ update_outputs (CoglRenderer *renderer)
}
}
- if (kms_output->saved_crtc)
- {
- output->width = kms_output->saved_crtc->width;
- output->height = kms_output->saved_crtc->height;
+ if (output->state->mode)
+ cogl_object_unref (output->state->mode);
+ output->state->mode = NULL;
- if (kms_output->saved_crtc->mode_valid)
- {
- drmModeModeInfo *mode = &kms_output->saved_crtc->mode;
- output->refresh_rate = mode->vrefresh;
- }
- }
- else
+ if (kms_output->saved_crtc &&
+ kms_output->saved_crtc->mode_valid)
{
- /* If there is no encoder associated with the connector then
- * there is no crtc mode and so there's currently no basis
- * to specify a width/height */
- output->width = 0;
- output->height = 0;
- }
+ output->state->mode =
+ find_mode (output->modes, kms_output->saved_crtc->mode.name);
+ cogl_object_ref (output->state->mode);
- _cogl_output_set_winsys_data (output,
- kms_output,
- kms_output_destroy_cb);
+ g_warn_if_fail (output->state->mode);
+ }
new_outputs = g_list_prepend (new_outputs, output);
}
@@ -476,6 +494,8 @@ update_outputs (CoglRenderer *renderer)
renderer->outputs = new_outputs;
drmModeFreeResources (resources);
+
+ _cogl_renderer_notify_outputs_changed (renderer);
}
static void
@@ -1242,6 +1262,419 @@ _cogl_winsys_onscreen_deinit (CoglOnscreen *onscreen)
onscreen->winsys = NULL;
}
+static drmModeProperty *
+get_connector_property (int fd,
+ drmModeConnector *connector,
+ const char *name)
+{
+ drmModeProperty *property;
+ int i;
+
+ for (i = 0; i < connector->count_props; i++)
+ {
+ property = drmModeGetProperty (fd, connector->props[i]);
+ if (!property)
+ continue;
+
+ if (strcmp (property->name, name) == 0)
+ return props;
+
+ drmModeFreeProperty (property);
+ }
+
+ return NULL;
+}
+
+/* XXX: NB: Don't assume that the output->state is a reliable cache
+ * of the real hardware state such that state changes can be avoided
+ * by looking for NOP changes between ->state and ->pending since we
+ * sometimes have to deal with the display being changed behind our
+ * back.
+ *
+ * TODO: Support incremental updates in certain cases
+ *
+ * XXX: The corner cases to consider...
+ *
+ * Q: How do we _commit an overlay configuration?
+ * A: During a _commit we always check that each overlay has
+ * an associated framebuffer source that has a valid
+ * ->next_bo or ->current_bo (otherwise we report an error)
+ * since we can't set a mode without a framebuffer and we
+ * don't want to be automatically allocating place holder
+ * framebuffers in corner cases or displaying undefined
+ * buffer contents.
+ *
+ * If we need to call drmModeSetCrtc() we will pass ->next_bo
+ * if available, otherwise ->current_bo.
+ *
+ * We always assume there could be outstanding rendering
+ * associated with a bo when calling drmModeSetCrtc() and so
+ * we explicitly synchronize with the GPU to make sure all
+ * rendering to the buffer is complete first.
+ *
+ * If we are using ->next_bo that implies there is currently
+ * a pending flip that hasn't completed. We should mark the
+ * pending flip as a "stale" flip by incrementing
+ * ->stale_flips++. Later when we reach page_flip_handler()
+ * we will check the ->stale_flips counter before
+ * decrementing it and whenever it is set we should only make
+ * sure to issue any necessary _FRAME_SYNC events but we
+ * should avoid making any updates to ->current_bo/->next_bo
+ * pointers.
+ *
+ *XXX: why does stale_flips need to be a counter instead of just
+ * a boolean
+ *
+ * After successfully calling drmModeSetCrtc() we insert a
+ * reference to the overlay in kms_onscreen->overlays.
+ *
+ * Q: what if there is an error when calling drmModeSetCrtc?
+ * A: We roll-back the whole configuration
+ *
+ * Q: What are the semantics for calling drmModeSetCrtc while
+ * there are pending page flips?
+ * A: Looking at the intel drm driver it looks like setting
+ * a mode starts by disabling the crtc, which involves
+ * waiting for pending flips so I think we can assume
+ * page flip events will *always* be delivered by drm.
+ *
+ * Q: How do we deal with roll-back when this is the first
+ * configuration to be committed?
+ * A: We should be assuming that update_outputs has been
+ * called before any configuration so we should know the
+ * previous state. XXX: what about previous framebuffer
+ * state? It seems likely that we'll need to special case
+ * restoring a saved state where the saved drmModeFB doesn't
+ * correspond to a CoglFramebuffer.
+ *
+ * Q: What if we disassociate a framebuffer from an overlay
+ * while it still has a pending flip? (considering what
+ * might go wrong if we then immediately tried to associate
+ * the framebuffer with a different overlay)
+ * A: XXX
+ *
+ * Q: When do we remove references from the
+ * kms_onscreen->overlays list?
+ * A: XXX
+ *
+ * Q: How do we handle _swap_buffers
+ * A: We iterate through each output that is associated with
+ * the onscreen framebuffer and for the first output
+ * we use drmModePageFlip (passing DRM_MODE_PAGE_FLIP_EVENT)
+ * to post the framebuffer for overlay0 and use
+ * drmModeSetPlane() for any additional overlays. For
+ * all other outputs we use drmModeSetCrtc to post the
+ * framebuffer for overlay0 while also using
+ * drmModeSetPlane() for additional overlays.
+ *
+ * Note: until we have the atomic page flipping api we
+ * don't have a very sane way of synchronizing changes
+ * to overlay planes.
+ *
+ * Note: we are intentionally only trying to synchronize
+ * with one output but we will probably want to provide
+ * api for controlling which output is synchronized.
+ *
+ * Q: Would it be better when handling multiple outputs to
+ * call drmModeSetCrtc() after being notified of the flip
+ * for the first output?
+ * A: Since using drmModeSetCrtc() implies needing to
+ * synchronize with the GPU to make sure rendering is
+ * complete, then waiting until the flip should minimize
+ * blocking on the CPU, especially in the case where the same
+ * buffer is being posted to multiple outputs since the flip
+ * should end up waiting for the GPU to finish for us so we
+ * won't have to.
+ *
+ * We should create a queue_swap_outputs utility for us to
+ * queue the swapping of all unsynchronized outputs.
+ *
+ *
+ * Q: What if we call _swap_buffers() before we have committed
+ * an output configuration associating the framebuffer with
+ * a hardware overlay?
+ * A: In this case we'll see that kms_output->overlays is NULL.
+ * We lock the front buffer, set it on ->next_bo and directly
+ * call page_flip_handler() to behave as if the flip
+ * completed immediately. This will make sure we issue a
+ * _FRAME_SYNC event for the swap and move the bo to
+ * ->current_bo.
+ *
+ *
+ * Q: How do we deal with an error in drmModePageFlip() also
+ * considering that after the error we might commit a
+ * new display configuration which will want to find
+ * a next/current_bo to reference.
+ * A: We can pretend there was no error and that the page
+ * flip completed immediately by calling page_flip_handler()
+ * directly in this case. This will make sure cogl
+ * dispatches a _FRAME_SYNC event for the frame otherwise
+ * applications may freeze. The main problem here is that
+ * we don't have meaningful timestamp data to pass to
+ * page_flip_handler(). XXX: wont this potentially break the
+ * invariable that we should be able to assume ->current_bo
+ * has no outstanding rendering?
+ *
+ * Q: How do we handle an onscreen framebuffer resize?
+ * A: We don't, you just have to create a new framebuffer
+ */
+static void
+_cogl_winsys_commit_outputs (CoglRenderer *renderer,
+ CoglError **error)
+{
+ CoglRendererEGL *egl_renderer = renderer->winsys;
+ CoglRendererKMS *kms_renderer = egl_renderer->platform;
+ GList *l;
+ CoglBool roll_back = FALSE;
+
+
+ /* If we hit an error at any point while committing the new
+ * configuration then we revert back to here with roll_back
+ * set to TRUE and instead re-commit the previous
+ * configuration */
+ROLL_BACK:
+
+
+ for (l = renderer->outputs; l; l = l->next)
+ {
+ CoglOutput *output = l->data;
+ CoglOutputKMS *kms_output = output->winsys;
+ CoglOutputState *state = roll_back ? output->state : output->pending;
+ CoglOverlay *overlay0;
+ drmModeConnector *connector;
+ drmModeProperty *property;
+ int i;
+
+ connector = drmModeGetConnector (kms_renderer->fd,
+ kms_output->connector_id);
+ if (!connector)
+ {
+ g_warn_if_reached ();
+ continue;
+ }
+
+ if (state->overlays)
+ overlay0 = state->overlays->data;
+ else
+ overlay0 = NULL;
+
+ /* If the output has no associated overlays then we assume
+ * it's ok to try and put it into DPMS_MODE_OFF */
+ dpms_mode = overlay0_new ? state_new->dpms_mode : DRM_MODE_DPMS_OFF;
+
+ property = get_connector_property (kms_renderer->fd,
+ connector,
+ "DPMS");
+ if (property)
+ {
+ int status = drmModeConnectorSetProperty (kms_renderer->fd,
+ connector->connector_id,
+ property->prop_id,
+ dpms_mode);
+ drmModeFreeProperty (property);
+
+ if (status < 0)
+ {
+ const char *dpms_mode_names[] = {
+ "ON",
+ "STANDBY",
+ "SUSPEND",
+ "OFF"
+ };
+
+ g_return_if_fail (roll_back == FALSE);
+
+ roll_back = TRUE;
+ roll_back_error =
+ g_strdup_printf ("Failed to set DPMS state for "
+ "connector %d to %s",
+ kms_output->connector_id,
+ dpms_mode_names[dpms_mode]);
+ goto ROLL_BACK;
+ }
+ }
+
+ if (!overlay0)
+ {
+ drmModeFreeConnector (connector);
+ continue;
+ }
+
+ if (!overlay0->onscreen_source)
+ {
+ g_return_if_fail (roll_back == FALSE);
+
+ roll_back = TRUE;
+ roll_back_error =
+ g_strdup_printf ("An overlay must be associated with an "
+ "onscreen framebuffer as a color source");
+ goto ROLL_BACK;
+ }
+
+ for (i = 0; i < connector->count_modes; i++)
+ {
+ drmModeInfo *mode_info = &connector->modes[i];
+ int n_planes;
+
+ if (strcmp (mode_info->name, state->mode->name) == 0)
+ {
+ CoglOnscreen *onscreen = overlay0->onscreen_source;
+ CoglOnscreenEGL *egl_onscreen = onscreen->winsys;
+ CoglOnscreenKMS *kms_onscreen = egl_onscreen->platform;
+ uint32_t fb_id;
+ int status;
+
+ if (overlay0->src_x != 0 ||
+ overlay0->src_y != 0 ||
+ overlay0->src_width != mode_info->width ||
+ overlay0->src_height != mode_info->height)
+ {
+ g_return_if_fail (roll_back == FALSE);
+
+ roll_back = TRUE;
+ roll_back_error =
+ g_strdup_printf ("On KMS the first overlay's source can "
+ "not be offset and its size must match "
+ "the mode resolution");
+ goto ROLL_BACK;
+ }
+
+ if (kms_onscreen->next_fb_id)
+ fb_id = kms_onscreen->next_fb_id;
+ else if (kms_onscreen->current_fb_id)
+ fb_id = kms_onscreen->current_fb_id;
+ else
+ {
+ g_return_if_fail (roll_back == FALSE);
+
+ roll_back = TRUE;
+ roll_back_error =
+ g_strdup_printf ("Can't commit output changes involving "
+ "an uninitialized (un-swapped) onscreen "
+ "framebuffer since it has no actual "
+ "data to scan out yet");
+ goto ROLL_BACK;
+ }
+
+ /* drmModeSetCrtc isn't synchronized with the GPU
+ * pipeline so we explicitly wait for any out standing
+ * rendering to complete before setting the new
+ * mode... */
+ cogl_framebuffer_finish (overlay0->onscreen_source);
+
+ status = drmModeSetCrtc (kms_renderer->fd,
+ kms_output->encoder->crtc_id,
+ fb_id, 0, 0,
+ &kms_output->connector_id, 1,
+ &kms_mode_info);
+ if (status < 0)
+ {
+ g_return_if_fail (roll_back == FALSE);
+
+ roll_back = TRUE;
+ roll_back_error =
+ g_strdup_printf ("KMS was unable to set the requested "
+ "mode (%s) on connector %d, possibly "
+ "due to a hardware limitation",
+ dpms_mode_names[dpms_mode],
+ kms_output->connector_id);
+ goto ROLL_BACK;
+ }
+ }
+
+ /*
+ * The remaining overlays should be setup via drmModeSetPlane()
+ */
+
+ for (n_planes = 0, l = state->overlays->next;
+ l;
+ n_planes++, l = l->next)
+ ;
+
+ if (n_planes)
+ {
+ drmModePlaneRes *planes =
+ drmModeGetPlaneResources (kms_renderer->fd);
+
+ if (!planes)
+ g_warn_if_reached ();
+
+ if (planes == NULL || planes->count_planes < n_planes)
+ {
+ g_return_if_fail (roll_back == FALSE);
+
+ roll_back = TRUE;
+ roll_back_error =
+ g_strdup_printf ("Hardware doesn't support enough "
+ "overlays to support configuration.");
+ goto ROLL_BACK;
+ }
+
+ for (n = 0, l = state->overlays->next; l; n++, l = l->next)
+ {
+ CoglOverlay *overlay = l->data;
+ CoglOnscreen *onscreen = overlay0->onscreen_source;
+ CoglOnscreenEGL *egl_onscreen = onscreen->winsys;
+ CoglOnscreenKMS *kms_onscreen = egl_onscreen->platform;
+ uint32_t fb_id;
+
+ if (!overlay->onscreen_source)
+ {
+ g_return_if_fail (roll_back == FALSE);
+
+ roll_back = TRUE;
+ roll_back_error =
+ g_strdup_printf ("An overlay must be associated with an "
+ "onscreen framebuffer as a color source");
+ goto ROLL_BACK;
+ }
+
+ if (kms_onscreen->next_fb_id)
+ fb_id = kms_onscreen->next_fb_id;
+ else if (kms_onscreen->current_fb_id)
+ fb_id = kms_onscreen->current_fb_id;
+ else
+ {
+ g_return_if_fail (roll_back == FALSE);
+
+ roll_back = TRUE;
+ roll_back_error =
+ g_strdup_printf ("Can't commit output changes involving "
+ "an uninitialized (un-swapped) onscreen "
+ "framebuffer since it has no actual "
+ "data to scan out yet");
+ goto ROLL_BACK;
+ }
+
+ if (drmModeSetPlane (kms_renderer->fd,
+ planes->planes[n],
+ kms_output->encoder->crtc_id,
+ fb_id,
+ /* FIXME: flags? */,
+ /* FIXME: ctc_x/y/w/h,
+ * src_x/y/w/h */) < 0)
+ {
+ g_return_if_fail (roll_back == FALSE);
+
+ roll_back = TRUE;
+ roll_back_error =
+ g_strdup_printf ("KMS was unable to setup the "
+ "overlays as requested, possibly "
+ "due to a hardware limitation.");
+ goto ROLL_BACK;
+ }
+ }
+
+ drmModeFreePlaneResources (planes);
+ }
+ }
+
+ drmModeFreeConnector (connector);
+
+ _cogl_output_update_state (output);
+ }
+}
+
static const CoglWinsysEGLVtable
_cogl_winsys_egl_vtable =
{
@@ -1282,6 +1715,8 @@ _cogl_winsys_egl_kms_get_vtable (void)
vtable.onscreen_swap_buffers_with_damage =
_cogl_winsys_onscreen_swap_buffers_with_damage;
+ vtable.commit_outputs = _cogl_winsys_commit_outputs;
+
vtable_inited = TRUE;
}