0,0 → 1,707 |
/* |
* Mesa 3-D graphics library |
* |
* Copyright (C) 2010 LunarG Inc. |
* Copyright (C) 2011 VMware Inc. All rights reserved. |
* |
* Permission is hereby granted, free of charge, to any person obtaining a |
* copy of this software and associated documentation files (the "Software"), |
* to deal in the Software without restriction, including without limitation |
* the rights to use, copy, modify, merge, publish, distribute, sublicense, |
* and/or sell copies of the Software, and to permit persons to whom the |
* Software is furnished to do so, subject to the following conditions: |
* |
* The above copyright notice and this permission notice shall be included |
* in all copies or substantial portions of the Software. |
* |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
* DEALINGS IN THE SOFTWARE. |
* |
* Authors: |
* Chia-I Wu <olv@lunarg.com> |
* Thomas Hellstrom <thellstrom@vmware.com> |
*/ |
|
#include "util/u_memory.h" |
#include "util/u_inlines.h" |
#include "egllog.h" |
|
#include "native_drm.h" |
|
static boolean |
drm_surface_validate(struct native_surface *nsurf, uint attachment_mask, |
unsigned int *seq_num, struct pipe_resource **textures, |
int *width, int *height) |
{ |
struct drm_surface *drmsurf = drm_surface(nsurf); |
|
if (!resource_surface_add_resources(drmsurf->rsurf, attachment_mask)) |
return FALSE; |
if (textures) |
resource_surface_get_resources(drmsurf->rsurf, textures, attachment_mask); |
|
if (seq_num) |
*seq_num = drmsurf->sequence_number; |
if (width) |
*width = drmsurf->width; |
if (height) |
*height = drmsurf->height; |
|
return TRUE; |
} |
|
/** |
* Add textures as DRM framebuffers. |
*/ |
static boolean |
drm_surface_init_framebuffers(struct native_surface *nsurf, boolean need_back) |
{ |
struct drm_surface *drmsurf = drm_surface(nsurf); |
struct drm_display *drmdpy = drmsurf->drmdpy; |
int num_framebuffers = (need_back) ? 2 : 1; |
int i, err; |
|
for (i = 0; i < num_framebuffers; i++) { |
struct drm_framebuffer *fb; |
enum native_attachment natt; |
struct winsys_handle whandle; |
uint block_bits; |
|
if (i == 0) { |
fb = &drmsurf->front_fb; |
natt = NATIVE_ATTACHMENT_FRONT_LEFT; |
} |
else { |
fb = &drmsurf->back_fb; |
natt = NATIVE_ATTACHMENT_BACK_LEFT; |
} |
|
if (!fb->texture) { |
/* make sure the texture has been allocated */ |
resource_surface_add_resources(drmsurf->rsurf, 1 << natt); |
fb->texture = |
resource_surface_get_single_resource(drmsurf->rsurf, natt); |
if (!fb->texture) |
return FALSE; |
} |
|
/* already initialized */ |
if (fb->buffer_id) |
continue; |
|
/* TODO detect the real value */ |
fb->is_passive = TRUE; |
|
memset(&whandle, 0, sizeof(whandle)); |
whandle.type = DRM_API_HANDLE_TYPE_KMS; |
|
if (!drmdpy->base.screen->resource_get_handle(drmdpy->base.screen, |
fb->texture, &whandle)) |
return FALSE; |
|
block_bits = util_format_get_blocksizebits(drmsurf->color_format); |
err = drmModeAddFB(drmdpy->fd, drmsurf->width, drmsurf->height, |
block_bits, block_bits, whandle.stride, whandle.handle, |
&fb->buffer_id); |
if (err) { |
fb->buffer_id = 0; |
return FALSE; |
} |
} |
|
return TRUE; |
} |
|
static boolean |
drm_surface_flush_frontbuffer(struct native_surface *nsurf) |
{ |
#ifdef DRM_MODE_FEATURE_DIRTYFB |
struct drm_surface *drmsurf = drm_surface(nsurf); |
struct drm_display *drmdpy = drmsurf->drmdpy; |
|
if (drmsurf->front_fb.is_passive) |
drmModeDirtyFB(drmdpy->fd, drmsurf->front_fb.buffer_id, NULL, 0); |
#endif |
|
return TRUE; |
} |
|
static boolean |
drm_surface_copy_swap(struct native_surface *nsurf) |
{ |
struct drm_surface *drmsurf = drm_surface(nsurf); |
struct drm_display *drmdpy = drmsurf->drmdpy; |
|
(void) resource_surface_throttle(drmsurf->rsurf); |
if (!resource_surface_copy_swap(drmsurf->rsurf, &drmdpy->base)) |
return FALSE; |
|
(void) resource_surface_flush(drmsurf->rsurf, &drmdpy->base); |
if (!drm_surface_flush_frontbuffer(nsurf)) |
return FALSE; |
|
drmsurf->sequence_number++; |
|
return TRUE; |
} |
|
static boolean |
drm_surface_swap_buffers(struct native_surface *nsurf) |
{ |
struct drm_surface *drmsurf = drm_surface(nsurf); |
struct drm_crtc *drmcrtc = &drmsurf->current_crtc; |
struct drm_display *drmdpy = drmsurf->drmdpy; |
struct drm_framebuffer tmp_fb; |
int err; |
|
if (!drmsurf->have_pageflip) |
return drm_surface_copy_swap(nsurf); |
|
if (!drmsurf->back_fb.buffer_id) { |
if (!drm_surface_init_framebuffers(&drmsurf->base, TRUE)) |
return FALSE; |
} |
|
if (drmsurf->is_shown && drmcrtc->crtc) { |
err = drmModePageFlip(drmdpy->fd, drmcrtc->crtc->crtc_id, |
drmsurf->back_fb.buffer_id, 0, NULL); |
if (err) { |
drmsurf->have_pageflip = FALSE; |
return drm_surface_copy_swap(nsurf); |
} |
} |
|
/* swap the buffers */ |
tmp_fb = drmsurf->front_fb; |
drmsurf->front_fb = drmsurf->back_fb; |
drmsurf->back_fb = tmp_fb; |
|
resource_surface_swap_buffers(drmsurf->rsurf, |
NATIVE_ATTACHMENT_FRONT_LEFT, NATIVE_ATTACHMENT_BACK_LEFT, FALSE); |
/* the front/back textures are swapped */ |
drmsurf->sequence_number++; |
drmdpy->event_handler->invalid_surface(&drmdpy->base, |
&drmsurf->base, drmsurf->sequence_number); |
|
return TRUE; |
} |
|
static boolean |
drm_surface_present(struct native_surface *nsurf, |
const struct native_present_control *ctrl) |
{ |
boolean ret; |
|
if (ctrl->swap_interval) |
return FALSE; |
|
switch (ctrl->natt) { |
case NATIVE_ATTACHMENT_FRONT_LEFT: |
ret = drm_surface_flush_frontbuffer(nsurf); |
break; |
case NATIVE_ATTACHMENT_BACK_LEFT: |
if (ctrl->preserve) |
ret = drm_surface_copy_swap(nsurf); |
else |
ret = drm_surface_swap_buffers(nsurf); |
break; |
default: |
ret = FALSE; |
break; |
} |
|
return ret; |
} |
|
static void |
drm_surface_wait(struct native_surface *nsurf) |
{ |
struct drm_surface *drmsurf = drm_surface(nsurf); |
|
resource_surface_wait(drmsurf->rsurf); |
} |
|
static void |
drm_surface_destroy(struct native_surface *nsurf) |
{ |
struct drm_surface *drmsurf = drm_surface(nsurf); |
|
resource_surface_wait(drmsurf->rsurf); |
if (drmsurf->current_crtc.crtc) |
drmModeFreeCrtc(drmsurf->current_crtc.crtc); |
|
if (drmsurf->front_fb.buffer_id) |
drmModeRmFB(drmsurf->drmdpy->fd, drmsurf->front_fb.buffer_id); |
pipe_resource_reference(&drmsurf->front_fb.texture, NULL); |
|
if (drmsurf->back_fb.buffer_id) |
drmModeRmFB(drmsurf->drmdpy->fd, drmsurf->back_fb.buffer_id); |
pipe_resource_reference(&drmsurf->back_fb.texture, NULL); |
|
resource_surface_destroy(drmsurf->rsurf); |
FREE(drmsurf); |
} |
|
static struct drm_surface * |
drm_display_create_surface(struct native_display *ndpy, |
const struct native_config *nconf, |
uint width, uint height) |
{ |
struct drm_display *drmdpy = drm_display(ndpy); |
struct drm_config *drmconf = drm_config(nconf); |
struct drm_surface *drmsurf; |
|
drmsurf = CALLOC_STRUCT(drm_surface); |
if (!drmsurf) |
return NULL; |
|
drmsurf->drmdpy = drmdpy; |
drmsurf->color_format = drmconf->base.color_format; |
drmsurf->width = width; |
drmsurf->height = height; |
drmsurf->have_pageflip = TRUE; |
|
drmsurf->rsurf = resource_surface_create(drmdpy->base.screen, |
drmsurf->color_format, |
PIPE_BIND_RENDER_TARGET | |
PIPE_BIND_SAMPLER_VIEW | |
PIPE_BIND_DISPLAY_TARGET | |
PIPE_BIND_SCANOUT); |
if (!drmsurf->rsurf) { |
FREE(drmsurf); |
return NULL; |
} |
|
resource_surface_set_size(drmsurf->rsurf, drmsurf->width, drmsurf->height); |
|
drmsurf->base.destroy = drm_surface_destroy; |
drmsurf->base.present = drm_surface_present; |
drmsurf->base.validate = drm_surface_validate; |
drmsurf->base.wait = drm_surface_wait; |
|
return drmsurf; |
} |
|
struct native_surface * |
drm_display_create_surface_from_resource(struct native_display *ndpy, |
struct pipe_resource *resource) |
{ |
struct drm_display *drmdpy = drm_display(ndpy); |
struct drm_surface *drmsurf; |
enum native_attachment natt = NATIVE_ATTACHMENT_FRONT_LEFT; |
|
drmsurf = CALLOC_STRUCT(drm_surface); |
if (!drmsurf) |
return NULL; |
|
drmsurf->drmdpy = drmdpy; |
drmsurf->color_format = resource->format; |
drmsurf->width = resource->width0; |
drmsurf->height = resource->height0; |
drmsurf->have_pageflip = FALSE; |
|
drmsurf->rsurf = resource_surface_create(drmdpy->base.screen, |
drmsurf->color_format, |
PIPE_BIND_RENDER_TARGET | |
PIPE_BIND_SAMPLER_VIEW | |
PIPE_BIND_DISPLAY_TARGET | |
PIPE_BIND_SCANOUT); |
|
resource_surface_import_resource(drmsurf->rsurf, natt, resource); |
|
drmsurf->base.destroy = drm_surface_destroy; |
drmsurf->base.present = drm_surface_present; |
drmsurf->base.validate = drm_surface_validate; |
drmsurf->base.wait = drm_surface_wait; |
|
return &drmsurf->base; |
} |
|
|
/** |
* Choose a CRTC that supports all given connectors. |
*/ |
static uint32_t |
drm_display_choose_crtc(struct native_display *ndpy, |
uint32_t *connectors, int num_connectors) |
{ |
struct drm_display *drmdpy = drm_display(ndpy); |
int idx; |
|
for (idx = 0; idx < drmdpy->resources->count_crtcs; idx++) { |
boolean found_crtc = TRUE; |
int i, j; |
|
for (i = 0; i < num_connectors; i++) { |
drmModeConnectorPtr connector; |
int encoder_idx = -1; |
|
connector = drmModeGetConnector(drmdpy->fd, connectors[i]); |
if (!connector) { |
found_crtc = FALSE; |
break; |
} |
|
/* find an encoder the CRTC supports */ |
for (j = 0; j < connector->count_encoders; j++) { |
drmModeEncoderPtr encoder = |
drmModeGetEncoder(drmdpy->fd, connector->encoders[j]); |
if (encoder->possible_crtcs & (1 << idx)) { |
encoder_idx = j; |
break; |
} |
drmModeFreeEncoder(encoder); |
} |
|
drmModeFreeConnector(connector); |
if (encoder_idx < 0) { |
found_crtc = FALSE; |
break; |
} |
} |
|
if (found_crtc) |
break; |
} |
|
if (idx >= drmdpy->resources->count_crtcs) { |
_eglLog(_EGL_WARNING, |
"failed to find a CRTC that supports the given %d connectors", |
num_connectors); |
return 0; |
} |
|
return drmdpy->resources->crtcs[idx]; |
} |
|
/** |
* Remember the original CRTC status and set the CRTC |
*/ |
static boolean |
drm_display_set_crtc(struct native_display *ndpy, int crtc_idx, |
uint32_t buffer_id, uint32_t x, uint32_t y, |
uint32_t *connectors, int num_connectors, |
drmModeModeInfoPtr mode) |
{ |
struct drm_display *drmdpy = drm_display(ndpy); |
struct drm_crtc *drmcrtc = &drmdpy->saved_crtcs[crtc_idx]; |
uint32_t crtc_id; |
int err; |
|
if (drmcrtc->crtc) { |
crtc_id = drmcrtc->crtc->crtc_id; |
} |
else { |
int count = 0, i; |
|
/* |
* Choose the CRTC once. It could be more dynamic, but let's keep it |
* simple for now. |
*/ |
crtc_id = drm_display_choose_crtc(&drmdpy->base, |
connectors, num_connectors); |
|
/* save the original CRTC status */ |
drmcrtc->crtc = drmModeGetCrtc(drmdpy->fd, crtc_id); |
if (!drmcrtc->crtc) |
return FALSE; |
|
for (i = 0; i < drmdpy->num_connectors; i++) { |
struct drm_connector *drmconn = &drmdpy->connectors[i]; |
drmModeConnectorPtr connector = drmconn->connector; |
drmModeEncoderPtr encoder; |
|
encoder = drmModeGetEncoder(drmdpy->fd, connector->encoder_id); |
if (encoder) { |
if (encoder->crtc_id == crtc_id) { |
drmcrtc->connectors[count++] = connector->connector_id; |
if (count >= Elements(drmcrtc->connectors)) |
break; |
} |
drmModeFreeEncoder(encoder); |
} |
} |
|
drmcrtc->num_connectors = count; |
} |
|
err = drmModeSetCrtc(drmdpy->fd, crtc_id, buffer_id, x, y, |
connectors, num_connectors, mode); |
if (err) { |
drmModeFreeCrtc(drmcrtc->crtc); |
drmcrtc->crtc = NULL; |
drmcrtc->num_connectors = 0; |
|
return FALSE; |
} |
|
return TRUE; |
} |
|
static boolean |
drm_display_program(struct native_display *ndpy, int crtc_idx, |
struct native_surface *nsurf, uint x, uint y, |
const struct native_connector **nconns, int num_nconns, |
const struct native_mode *nmode) |
{ |
struct drm_display *drmdpy = drm_display(ndpy); |
struct drm_surface *drmsurf = drm_surface(nsurf); |
const struct drm_mode *drmmode = drm_mode(nmode); |
uint32_t connector_ids[32]; |
uint32_t buffer_id; |
drmModeModeInfo mode_tmp, *mode; |
int i; |
|
if (num_nconns > Elements(connector_ids)) { |
_eglLog(_EGL_WARNING, "too many connectors (%d)", num_nconns); |
num_nconns = Elements(connector_ids); |
} |
|
if (drmsurf) { |
if (!drm_surface_init_framebuffers(&drmsurf->base, FALSE)) |
return FALSE; |
|
buffer_id = drmsurf->front_fb.buffer_id; |
/* the mode argument of drmModeSetCrtc is not constified */ |
mode_tmp = drmmode->mode; |
mode = &mode_tmp; |
} |
else { |
/* disable the CRTC */ |
buffer_id = 0; |
mode = NULL; |
num_nconns = 0; |
} |
|
for (i = 0; i < num_nconns; i++) { |
struct drm_connector *drmconn = drm_connector(nconns[i]); |
connector_ids[i] = drmconn->connector->connector_id; |
} |
|
if (!drm_display_set_crtc(&drmdpy->base, crtc_idx, buffer_id, x, y, |
connector_ids, num_nconns, mode)) { |
_eglLog(_EGL_WARNING, "failed to set CRTC %d", crtc_idx); |
|
return FALSE; |
} |
|
if (drmdpy->shown_surfaces[crtc_idx]) |
drmdpy->shown_surfaces[crtc_idx]->is_shown = FALSE; |
drmdpy->shown_surfaces[crtc_idx] = drmsurf; |
|
/* remember the settings for buffer swapping */ |
if (drmsurf) { |
uint32_t crtc_id = drmdpy->saved_crtcs[crtc_idx].crtc->crtc_id; |
struct drm_crtc *drmcrtc = &drmsurf->current_crtc; |
|
if (drmcrtc->crtc) |
drmModeFreeCrtc(drmcrtc->crtc); |
drmcrtc->crtc = drmModeGetCrtc(drmdpy->fd, crtc_id); |
|
assert(num_nconns < Elements(drmcrtc->connectors)); |
memcpy(drmcrtc->connectors, connector_ids, |
sizeof(*connector_ids) * num_nconns); |
drmcrtc->num_connectors = num_nconns; |
|
drmsurf->is_shown = TRUE; |
} |
|
return TRUE; |
} |
|
static const struct native_mode ** |
drm_display_get_modes(struct native_display *ndpy, |
const struct native_connector *nconn, |
int *num_modes) |
{ |
struct drm_display *drmdpy = drm_display(ndpy); |
struct drm_connector *drmconn = drm_connector(nconn); |
const struct native_mode **nmodes_return; |
int count, i; |
|
/* delete old data */ |
if (drmconn->connector) { |
drmModeFreeConnector(drmconn->connector); |
FREE(drmconn->drm_modes); |
|
drmconn->connector = NULL; |
drmconn->drm_modes = NULL; |
drmconn->num_modes = 0; |
} |
|
/* detect again */ |
drmconn->connector = drmModeGetConnector(drmdpy->fd, drmconn->connector_id); |
if (!drmconn->connector) |
return NULL; |
|
count = drmconn->connector->count_modes; |
drmconn->drm_modes = CALLOC(count, sizeof(*drmconn->drm_modes)); |
if (!drmconn->drm_modes) { |
drmModeFreeConnector(drmconn->connector); |
drmconn->connector = NULL; |
|
return NULL; |
} |
|
for (i = 0; i < count; i++) { |
struct drm_mode *drmmode = &drmconn->drm_modes[i]; |
drmModeModeInfoPtr mode = &drmconn->connector->modes[i]; |
|
drmmode->mode = *mode; |
|
drmmode->base.desc = drmmode->mode.name; |
drmmode->base.width = drmmode->mode.hdisplay; |
drmmode->base.height = drmmode->mode.vdisplay; |
drmmode->base.refresh_rate = drmmode->mode.vrefresh; |
/* not all kernels have vrefresh = refresh_rate * 1000 */ |
if (drmmode->base.refresh_rate < 1000) |
drmmode->base.refresh_rate *= 1000; |
} |
|
nmodes_return = MALLOC(count * sizeof(*nmodes_return)); |
if (nmodes_return) { |
for (i = 0; i < count; i++) |
nmodes_return[i] = &drmconn->drm_modes[i].base; |
if (num_modes) |
*num_modes = count; |
} |
|
return nmodes_return; |
} |
|
static const struct native_connector ** |
drm_display_get_connectors(struct native_display *ndpy, int *num_connectors, |
int *num_crtc) |
{ |
struct drm_display *drmdpy = drm_display(ndpy); |
const struct native_connector **connectors; |
int i; |
|
if (!drmdpy->connectors) { |
drmdpy->connectors = |
CALLOC(drmdpy->resources->count_connectors, sizeof(*drmdpy->connectors)); |
if (!drmdpy->connectors) |
return NULL; |
|
for (i = 0; i < drmdpy->resources->count_connectors; i++) { |
struct drm_connector *drmconn = &drmdpy->connectors[i]; |
|
drmconn->connector_id = drmdpy->resources->connectors[i]; |
/* drmconn->connector is allocated when the modes are asked */ |
} |
|
drmdpy->num_connectors = drmdpy->resources->count_connectors; |
} |
|
connectors = MALLOC(drmdpy->num_connectors * sizeof(*connectors)); |
if (connectors) { |
for (i = 0; i < drmdpy->num_connectors; i++) |
connectors[i] = &drmdpy->connectors[i].base; |
if (num_connectors) |
*num_connectors = drmdpy->num_connectors; |
} |
|
if (num_crtc) |
*num_crtc = drmdpy->resources->count_crtcs; |
|
return connectors; |
} |
|
static struct native_surface * |
drm_display_create_scanout_surface(struct native_display *ndpy, |
const struct native_config *nconf, |
uint width, uint height) |
{ |
struct drm_surface *drmsurf; |
|
drmsurf = drm_display_create_surface(ndpy, nconf, width, height); |
return &drmsurf->base; |
} |
|
static struct native_display_modeset drm_display_modeset = { |
.get_connectors = drm_display_get_connectors, |
.get_modes = drm_display_get_modes, |
.create_scanout_surface = drm_display_create_scanout_surface, |
.program = drm_display_program |
}; |
|
void |
drm_display_fini_modeset(struct native_display *ndpy) |
{ |
struct drm_display *drmdpy = drm_display(ndpy); |
int i; |
|
if (drmdpy->connectors) { |
for (i = 0; i < drmdpy->num_connectors; i++) { |
struct drm_connector *drmconn = &drmdpy->connectors[i]; |
if (drmconn->connector) { |
drmModeFreeConnector(drmconn->connector); |
FREE(drmconn->drm_modes); |
} |
} |
FREE(drmdpy->connectors); |
} |
|
FREE(drmdpy->shown_surfaces); |
drmdpy->shown_surfaces = NULL; |
|
if (drmdpy->saved_crtcs) { |
for (i = 0; i < drmdpy->resources->count_crtcs; i++) { |
struct drm_crtc *drmcrtc = &drmdpy->saved_crtcs[i]; |
|
if (drmcrtc->crtc) { |
/* restore crtc */ |
drmModeSetCrtc(drmdpy->fd, drmcrtc->crtc->crtc_id, |
drmcrtc->crtc->buffer_id, drmcrtc->crtc->x, drmcrtc->crtc->y, |
drmcrtc->connectors, drmcrtc->num_connectors, |
&drmcrtc->crtc->mode); |
|
drmModeFreeCrtc(drmcrtc->crtc); |
} |
} |
FREE(drmdpy->saved_crtcs); |
} |
|
if (drmdpy->resources) { |
drmModeFreeResources(drmdpy->resources); |
drmdpy->resources = NULL; |
} |
|
drmdpy->base.modeset = NULL; |
} |
|
boolean |
drm_display_init_modeset(struct native_display *ndpy) |
{ |
struct drm_display *drmdpy = drm_display(ndpy); |
|
/* resources are fixed, unlike crtc, connector, or encoder */ |
drmdpy->resources = drmModeGetResources(drmdpy->fd); |
if (!drmdpy->resources) { |
_eglLog(_EGL_DEBUG, "Failed to get KMS resources. Disable modeset."); |
return FALSE; |
} |
|
drmdpy->saved_crtcs = |
CALLOC(drmdpy->resources->count_crtcs, sizeof(*drmdpy->saved_crtcs)); |
if (!drmdpy->saved_crtcs) { |
drm_display_fini_modeset(&drmdpy->base); |
return FALSE; |
} |
|
drmdpy->shown_surfaces = |
CALLOC(drmdpy->resources->count_crtcs, sizeof(*drmdpy->shown_surfaces)); |
if (!drmdpy->shown_surfaces) { |
drm_display_fini_modeset(&drmdpy->base); |
return FALSE; |
} |
|
drmdpy->base.modeset = &drm_display_modeset; |
|
return TRUE; |
} |