libliftoff/display.c
Simon Ser d90d7bdc36
Check plane is compatible with CRTC
This allows us to rule out some potential solutions.

Closes: https://github.com/emersion/libhwc/issues/2
2019-09-08 17:41:25 +03:00

386 lines
9.4 KiB
C

#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "private.h"
static struct hwc_plane *plane_create(struct hwc_display *display, int32_t id)
{
struct hwc_plane *plane;
drmModePlane *drm_plane;
drmModeObjectProperties *drm_props;
uint32_t i;
drmModePropertyRes *drm_prop;
struct hwc_plane_property *prop;
plane = calloc(1, sizeof(*plane));
if (plane == NULL) {
return NULL;
}
drm_plane = drmModeGetPlane(display->drm_fd, id);
if (drm_plane == NULL) {
return NULL;
}
plane->id = drm_plane->plane_id;
plane->possible_crtcs = drm_plane->possible_crtcs;
drmModeFreePlane(drm_plane);
drm_props = drmModeObjectGetProperties(display->drm_fd, id,
DRM_MODE_OBJECT_PLANE);
if (drm_props == NULL) {
return NULL;
}
plane->props = calloc(drm_props->count_props,
sizeof(struct hwc_plane_property));
if (plane->props == NULL) {
drmModeFreeObjectProperties(drm_props);
return NULL;
}
for (i = 0; i < drm_props->count_props; i++) {
drm_prop = drmModeGetProperty(display->drm_fd,
drm_props->props[i]);
if (drm_prop == NULL) {
drmModeFreeObjectProperties(drm_props);
return NULL;
}
prop = &plane->props[i];
memcpy(prop->name, drm_prop->name, sizeof(prop->name));
prop->id = drm_prop->prop_id;
drmModeFreeProperty(drm_prop);
plane->props_len++;
}
drmModeFreeObjectProperties(drm_props);
hwc_list_insert(display->planes.prev, &plane->link);
return plane;
}
static void plane_destroy(struct hwc_plane *plane)
{
hwc_list_remove(&plane->link);
free(plane->props);
free(plane);
}
struct hwc_display *hwc_display_create(int drm_fd)
{
struct hwc_display *display;
drmModeRes *drm_res;
drmModePlaneRes *drm_plane_res;
uint32_t i;
display = calloc(1, sizeof(*display));
if (display == NULL) {
return NULL;
}
display->drm_fd = dup(drm_fd);
if (display->drm_fd < 0) {
hwc_display_destroy(display);
return NULL;
}
hwc_list_init(&display->planes);
hwc_list_init(&display->outputs);
drm_res = drmModeGetResources(drm_fd);
if (drm_res == NULL) {
hwc_display_destroy(display);
return NULL;
}
display->crtcs = malloc(drm_res->count_crtcs * sizeof(uint32_t));
if (display->crtcs == NULL) {
drmModeFreeResources(drm_res);
hwc_display_destroy(display);
return NULL;
}
display->crtcs_len = drm_res->count_crtcs;
memcpy(display->crtcs, drm_res->crtcs,
drm_res->count_crtcs * sizeof(uint32_t));
drmModeFreeResources(drm_res);
/* TODO: allow users to choose which layers to hand over */
drm_plane_res = drmModeGetPlaneResources(drm_fd);
if (drm_plane_res == NULL) {
hwc_display_destroy(display);
return NULL;
}
for (i = 0; i < drm_plane_res->count_planes; i++) {
if (plane_create(display, drm_plane_res->planes[i]) == NULL) {
hwc_display_destroy(display);
return NULL;
}
}
drmModeFreePlaneResources(drm_plane_res);
return display;
}
void hwc_display_destroy(struct hwc_display *display)
{
struct hwc_plane *plane, *tmp;
close(display->drm_fd);
hwc_list_for_each_safe(plane, tmp, &display->planes, link) {
plane_destroy(plane);
}
free(display->crtcs);
free(display);
}
static struct hwc_plane_property *plane_get_property(struct hwc_plane *plane,
const char *name)
{
size_t i;
for (i = 0; i < plane->props_len; i++) {
if (strcmp(plane->props[i].name, name) == 0) {
return &plane->props[i];
}
}
return NULL;
}
static bool plane_set_prop(struct hwc_plane *plane, drmModeAtomicReq *req,
struct hwc_plane_property *prop, uint64_t value)
{
int ret;
fprintf(stderr, " Setting %s = %"PRIu64"\n", prop->name, value);
ret = drmModeAtomicAddProperty(req, plane->id, prop->id, value);
if (ret < 0) {
perror("drmModeAtomicAddProperty");
return false;
}
return true;
}
static bool plane_apply(struct hwc_plane *plane, struct hwc_layer *layer,
drmModeAtomicReq *req)
{
size_t i;
struct hwc_layer_property *layer_prop;
struct hwc_plane_property *plane_prop;
if (layer == NULL) {
plane_prop = plane_get_property(plane, "FB_ID");
assert(plane_prop);
return plane_set_prop(plane, req, plane_prop, 0);
}
plane_prop = plane_get_property(plane, "CRTC_ID");
assert(plane_prop);
if (!plane_set_prop(plane, req, plane_prop, layer->output->crtc_id)) {
return false;
}
for (i = 0; i < layer->props_len; i++) {
layer_prop = &layer->props[i];
plane_prop = plane_get_property(plane, layer_prop->name);
if (plane_prop == NULL) {
fprintf(stderr, "failed to find property %s\n",
layer_prop->name);
return false;
}
if (!plane_set_prop(plane, req, plane_prop, layer_prop->value)) {
return false;
}
}
return true;
}
struct plane_alloc {
drmModeAtomicReq *req;
size_t planes_len;
struct hwc_layer **current;
struct hwc_layer **best;
int best_score;
};
bool output_choose_layers(struct hwc_output *output, struct plane_alloc *alloc,
struct hwc_list *cur, size_t plane_idx, int score)
{
struct hwc_display *display;
struct hwc_plane *plane;
struct hwc_layer *layer;
int cursor, ret;
size_t remaining_planes, i;
bool found;
display = output->display;
if (cur == &display->planes) { /* Allocation finished */
if (score > alloc->best_score) {
/* We found a better allocation */
alloc->best_score = score;
memcpy(alloc->best, alloc->current,
alloc->planes_len * sizeof(struct hwc_layer *));
}
return true;
}
plane = hwc_container_of(cur, plane, link);
remaining_planes = alloc->planes_len - plane_idx;
if (alloc->best_score >= (int)remaining_planes) {
/* Even if we find a layer for all remaining planes, we won't
* find a better allocation. Give up. */
return true;
}
cursor = drmModeAtomicGetCursor(alloc->req);
if (plane->layer != NULL) {
goto skip;
}
if ((plane->possible_crtcs & (1 << output->crtc_index)) == 0) {
goto skip;
}
hwc_list_for_each(layer, &output->layers, link) {
if (layer->plane != NULL) {
continue;
}
found = false;
for (i = 0; i < plane_idx; i++) {
if (alloc->current[i] == layer) {
found = true;
break;
}
}
if (found) {
continue;
}
/* Try to use this layer for the current plane */
alloc->current[plane_idx] = layer;
fprintf(stderr, "Trying to apply layer %p with plane %d...\n",
(void *)layer, plane->id);
if (!plane_apply(plane, layer, alloc->req)) {
return false;
}
ret = drmModeAtomicCommit(display->drm_fd, alloc->req,
DRM_MODE_ATOMIC_TEST_ONLY, NULL);
if (ret == 0) {
fprintf(stderr, "Success\n");
/* Continue with the next plane */
if (!output_choose_layers(output, alloc, cur->next,
plane_idx + 1, score + 1)) {
return false;
}
} else if (-ret != EINVAL && -ret != ERANGE) {
perror("drmModeAtomicCommit");
return false;
}
drmModeAtomicSetCursor(alloc->req, cursor);
}
skip:
/* Try not to use the current plane */
alloc->current[plane_idx] = NULL;
if (!output_choose_layers(output, alloc, cur->next,
plane_idx + 1, score)) {
return false;
}
drmModeAtomicSetCursor(alloc->req, cursor);
return true;
}
bool hwc_display_apply(struct hwc_display *display, drmModeAtomicReq *req)
{
struct hwc_output *output;
struct hwc_plane *plane;
struct hwc_layer *layer;
struct plane_alloc alloc;
size_t i;
/* Unset all existing plane and layer mappings.
TODO: incremental updates keeping old configuration if possible */
hwc_list_for_each(plane, &display->planes, link) {
if (plane->layer != NULL) {
plane->layer->plane = NULL;
plane->layer = NULL;
}
}
/* Disable all planes. Do it before building mappings to make sure not
to hit bandwidth limits because too many planes are enabled. */
hwc_list_for_each(plane, &display->planes, link) {
if (plane->layer == NULL) {
fprintf(stderr, "Disabling plane %d\n", plane->id);
if (!plane_apply(plane, NULL, req)) {
return false;
}
}
}
alloc.req = req;
alloc.planes_len = hwc_list_length(&display->planes);
alloc.current = malloc(alloc.planes_len * sizeof(*alloc.current));
alloc.best = malloc(alloc.planes_len * sizeof(*alloc.best));
if (alloc.current == NULL || alloc.best == NULL) {
perror("malloc");
return false;
}
/* TODO: maybe start by allocating the primary plane on each output to
* make sure we can display at least something without hitting bandwidth
* issues? Also: be fair when mapping planes to outputs, don't give all
* planes to a single output. Also: don't treat each output separately,
* allocate planes for all outputs at once. */
hwc_list_for_each(output, &display->outputs, link) {
/* For each plane, try to find a layer. Don't do it the other
* way around (ie. for each layer, try to find a plane) because
* some drivers want user-space to enable the primary plane
* before any other plane. */
alloc.best_score = 0;
memset(alloc.best, 0, alloc.planes_len * sizeof(*alloc.best));
if (!output_choose_layers(output, &alloc, display->planes.next,
0, 0)) {
return false;
}
fprintf(stderr, "Found plane allocation for output %p "
"with score=%d\n", (void *)output, alloc.best_score);
/* Apply the best allocation */
i = 0;
hwc_list_for_each(plane, &display->planes, link) {
layer = alloc.best[i];
i++;
if (layer == NULL) {
continue;
}
fprintf(stderr, "Assigning layer %p to plane %d\n",
(void *)layer, plane->id);
if (!plane_apply(plane, layer, req)) {
return false;
}
assert(plane->layer == NULL);
assert(layer->plane == NULL);
plane->layer = layer;
layer->plane = plane;
}
}
free(alloc.current);
free(alloc.best);
return true;
}