diff --git a/src/compositor-rpi.c b/src/compositor-rpi.c index f6a6f648..c4023afb 100644 --- a/src/compositor-rpi.c +++ b/src/compositor-rpi.c @@ -46,18 +46,112 @@ #include "compositor.h" #include "evdev.h" +/* + * Dispmanx API offers alpha-blended overlays for hardware compositing. + * The final composite consists of dispmanx elements, and their contents: + * the dispmanx resource assigned to the element. The elements may be + * scanned out directly, or composited to a temporary surface, depending on + * how the firmware decides to handle the scene. Updates to multiple elements + * may be queued in a single dispmanx update object, resulting in atomic and + * vblank synchronized display updates. + * + * To avoid tearing and display artifacts, the current dispmanx resource in a + * dispmanx element must not be touched. Therefore each element must be + * double-buffered, using two resources, the front and the back. The update + * sequence is: + * 0. the front resource is already in-use, the back resource is unused + * 1. write data into the back resource + * 2. submit an element update, back becomes in-use + * 3. swap back and front pointers (both are in-use now) + * 4. wait for update_submit completion, the new back resource becomes unused + * + * A resource may be destroyed only, when the update removing the element has + * completed. Otherwise you risk showing an incomplete composition. + * + * The dispmanx element used as the native window for EGL does not need + * manually allocated resources, EGL does double-buffering internally. + * Unfortunately it also means, that we cannot alternate between two + * buffers like the DRM backend does, since we have no control over what + * resources EGL uses. We are forced to use EGL_BUFFER_PRESERVED as the + * EGL_SWAP_BEHAVIOR to avoid repainting the whole output every frame. + * + * We also cannot bundle eglSwapBuffers into our own display update, which + * means that Weston's primary plane updates and the overlay updates may + * happen unsynchronized. + */ + +#ifndef ELEMENT_CHANGE_LAYER +/* copied from interface/vmcs_host/vc_vchi_dispmanx.h of userland.git */ +#define ELEMENT_CHANGE_LAYER (1<<0) +#define ELEMENT_CHANGE_OPACITY (1<<1) +#define ELEMENT_CHANGE_DEST_RECT (1<<2) +#define ELEMENT_CHANGE_SRC_RECT (1<<3) +#define ELEMENT_CHANGE_MASK_RESOURCE (1<<4) +#define ELEMENT_CHANGE_TRANSFORM (1<<5) +#endif + +/* Enabling this debugging incurs a significant performance hit */ +#if 0 +#define DBG(...) \ + weston_log(__VA_ARGS__) +#else +#define DBG(...) do {} while (0) +#endif + +/* If we had a fully featured vc_dispmanx_resource_write_data()... */ +/*#define HAVE_RESOURCE_WRITE_DATA_RECT 1*/ + struct rpi_compositor; +struct rpi_output; + +struct rpi_resource { + DISPMANX_RESOURCE_HANDLE_T handle; + int width; + int height; /* height of the image (valid pixel data) */ + int stride; /* bytes */ + int buffer_height; /* height of the buffer */ + VC_IMAGE_TYPE_T ifmt; +}; + +struct rpi_element { + struct wl_list link; + struct weston_plane plane; + struct rpi_output *output; + + DISPMANX_ELEMENT_HANDLE_T handle; + int layer; + int need_swap; + int single_buffer; + + struct rpi_resource resources[2]; + struct rpi_resource *front; + struct rpi_resource *back; + pixman_region32_t prev_damage; + + struct weston_surface *surface; + struct wl_listener surface_destroy_listener; +}; + +struct rpi_flippipe { + int readfd; + int writefd; + struct wl_event_source *source; +}; struct rpi_output { struct rpi_compositor *compositor; struct weston_output base; + int single_buffer; struct weston_mode mode; - struct wl_event_source *finish_frame_timer; + struct rpi_flippipe flippipe; DISPMANX_DISPLAY_HANDLE_T display; EGL_DISPMANX_WINDOW_T egl_window; DISPMANX_ELEMENT_HANDLE_T egl_element; + + struct wl_list element_list; /* struct rpi_element */ + struct wl_list old_element_list; /* struct rpi_element */ }; struct rpi_seat { @@ -75,6 +169,9 @@ struct rpi_compositor { struct udev *udev; struct tty *tty; + + int max_planes; /* per output, really */ + int single_buffer; }; static inline struct rpi_output * @@ -131,26 +228,729 @@ print_egl_error_state(void) egl_error_string(code), (long)code); } +static inline int +int_max(int a, int b) +{ + return a > b ? a : b; +} + +static void +rpi_resource_init(struct rpi_resource *resource) +{ + resource->handle = DISPMANX_NO_HANDLE; +} + +static void +rpi_resource_release(struct rpi_resource *resource) +{ + if (resource->handle == DISPMANX_NO_HANDLE) + return; + + vc_dispmanx_resource_delete(resource->handle); + DBG("resource %p release\n", resource); + resource->handle = DISPMANX_NO_HANDLE; +} + static int -rpi_finish_frame(void *data) +rpi_resource_realloc(struct rpi_resource *resource, VC_IMAGE_TYPE_T ifmt, + int width, int height, int stride, int buffer_height) +{ + uint32_t dummy; + + if (resource->handle != DISPMANX_NO_HANDLE && + resource->width == width && + resource->height == height && + resource->stride == stride && + resource->buffer_height == buffer_height && + resource->ifmt == ifmt) + return 0; + + rpi_resource_release(resource); + + /* NOTE: if stride is not a multiple of 16 pixels in bytes, + * the vc_image_* functions may break. Dispmanx elements + * should be fine, though. Buffer_height probably has similar + * constraints, too. + */ + resource->handle = + vc_dispmanx_resource_create(ifmt, + width | (stride << 16), + height | (buffer_height << 16), + &dummy); + if (resource->handle == DISPMANX_NO_HANDLE) + return -1; + + resource->width = width; + resource->height = height; + resource->stride = stride; + resource->buffer_height = buffer_height; + resource->ifmt = ifmt; + DBG("resource %p alloc\n", resource); + return 0; +} + +static VC_IMAGE_TYPE_T +shm_buffer_get_vc_format(struct wl_buffer *buffer) +{ + switch (wl_shm_buffer_get_format(buffer)) { + case WL_SHM_FORMAT_XRGB8888: + return VC_IMAGE_XRGB8888; + case WL_SHM_FORMAT_ARGB8888: + return VC_IMAGE_ARGB8888; + default: + /* invalid format */ + return VC_IMAGE_MIN; + } +} + +static int +rpi_resource_update(struct rpi_resource *resource, struct wl_buffer *buffer, + pixman_region32_t *region) +{ + pixman_region32_t write_region; + pixman_box32_t *r; + VC_RECT_T rect; + VC_IMAGE_TYPE_T ifmt; + uint32_t *pixels; + int width; + int height; + int stride; + int ret; +#ifdef HAVE_RESOURCE_WRITE_DATA_RECT + int n; +#endif + + if (!buffer) + return -1; + + ifmt = shm_buffer_get_vc_format(buffer); + width = wl_shm_buffer_get_width(buffer); + height = wl_shm_buffer_get_height(buffer); + stride = wl_shm_buffer_get_stride(buffer); + pixels = wl_shm_buffer_get_data(buffer); + + if (rpi_resource_realloc(resource, ifmt, width, height, + stride, height) < 0) + return -1; + + pixman_region32_init(&write_region); + pixman_region32_intersect_rect(&write_region, region, + 0, 0, width, height); + +#ifdef HAVE_RESOURCE_WRITE_DATA_RECT + /* XXX: Can this do a format conversion, so that scanout does not have to? */ + r = pixman_region32_rectangles(&write_region, &n); + while (n--) { + vc_dispmanx_rect_set(&rect, r[n].x1, r[n].y1, + r[n].x2 - r[n].x1, r[n].y2 - r[n].y1); + + ret = vc_dispmanx_resource_write_data_rect(resource->handle, + ifmt, stride, + pixels, &rect, + rect.x, rect.y); + DBG("%s: %p %ux%u@%u,%u, ret %d\n", __func__, resource, + rect.width, rect.height, rect.x, rect.y, ret); + if (ret) + break; + } +#else + /* vc_dispmanx_resource_write_data() ignores ifmt, + * rect.x, rect.width, and uses stride only for computing + * the size of the transfer as rect.height * stride. + * Therefore we can only write rows starting at x=0. + * To be able to write more than one scanline at a time, + * the resource must have been created with the same stride + * as used here, and we must write full scanlines. + */ + + r = pixman_region32_extents(&write_region); + vc_dispmanx_rect_set(&rect, 0, r->y1, width, r->y2 - r->y1); + ret = vc_dispmanx_resource_write_data(resource->handle, ifmt, + stride, pixels, &rect); + DBG("%s: %p %ux%u@%u,%u, ret %d\n", __func__, resource, + width, r->y2 - r->y1, 0, r->y1, ret); +#endif + + pixman_region32_fini(&write_region); + + return ret ? -1 : 0; +} + +static void +rpi_element_handle_surface_destroy(struct wl_listener *listener, void *data) +{ + struct rpi_element *element = + container_of(listener, struct rpi_element, + surface_destroy_listener); + + element->surface = NULL; +} + +static struct rpi_element * +rpi_element_create(struct rpi_output *output, struct weston_surface *surface) +{ + struct rpi_element *element; + + element = calloc(1, sizeof *element); + if (!element) + return NULL; + + element->output = output; + element->single_buffer = output->single_buffer; + element->handle = DISPMANX_NO_HANDLE; + rpi_resource_init(&element->resources[0]); + rpi_resource_init(&element->resources[1]); + element->front = &element->resources[0]; + + if (element->single_buffer) { + element->back = element->front; + } else { + element->back = &element->resources[1]; + } + + pixman_region32_init(&element->prev_damage); + + weston_plane_init(&element->plane, floor(surface->geometry.x), + floor(surface->geometry.y)); + + element->surface = surface; + element->surface_destroy_listener.notify = + rpi_element_handle_surface_destroy; + wl_signal_add(&surface->surface.resource.destroy_signal, + &element->surface_destroy_listener); + + wl_list_insert(output->element_list.prev, &element->link); + + return element; +} + +static void +rpi_element_destroy(struct rpi_element *element) +{ + struct weston_surface *surface = element->surface; + + if (surface) { + if (surface->plane == &element->plane) { + /* If a surface, that was on a plane, gets hidden, + * it will not appear in the repaint surface list, + * is never considered in rpi_output_assign_planes(), + * and hence can stay assigned to this element's plane. + * We need to reassign it here. + */ + DBG("surface %p (%dx%d@%.1f,%.1f) to primary plane*\n", + surface, + surface->geometry.width, surface->geometry.height, + surface->geometry.x, surface->geometry.y); + weston_surface_move_to_plane(surface, + &surface->compositor->primary_plane); + } + wl_list_remove(&element->surface_destroy_listener.link); + } + + wl_list_remove(&element->link); + weston_plane_release(&element->plane); + + if (element->handle != DISPMANX_NO_HANDLE) + weston_log("ERROR rpi: destroying on-screen element\n"); + + pixman_region32_fini(&element->prev_damage); + rpi_resource_release(&element->resources[0]); + rpi_resource_release(&element->resources[1]); + DBG("element %p destroyed (%u)\n", element, element->handle); + + free(element); +} + +static void +rpi_element_reuse(struct rpi_element *element) +{ + wl_list_remove(&element->link); + wl_list_insert(element->output->element_list.prev, &element->link); +} + +static void +rpi_element_schedule_destroy(struct rpi_element *element) +{ + wl_list_remove(&element->link); + wl_list_insert(element->output->old_element_list.prev, + &element->link); +} + +static int +rpi_element_damage(struct rpi_element *element, struct wl_buffer *buffer, + pixman_region32_t *damage) +{ + pixman_region32_t upload; + int ret; + + if (!pixman_region32_not_empty(damage)) + return 0; + + DBG("element %p update resource %p\n", element, element->back); + + if (element->single_buffer) { + ret = rpi_resource_update(element->back, buffer, damage); + } else { + pixman_region32_init(&upload); + pixman_region32_union(&upload, &element->prev_damage, damage); + ret = rpi_resource_update(element->back, buffer, &upload); + pixman_region32_fini(&upload); + } + + pixman_region32_copy(&element->prev_damage, damage); + element->need_swap = 1; + + return ret; +} + +static void +rpi_element_compute_rects(struct rpi_element *element, + VC_RECT_T *src_rect, VC_RECT_T *dst_rect) +{ + struct weston_output *output = &element->output->base; + int src_x, src_y; + int dst_x, dst_y; + int width, height; + + /* assume element->plane.{x,y} == element->surface->geometry.{x,y} */ + src_x = 0; + src_y = 0; + width = element->surface->geometry.width; + height = element->surface->geometry.height; + + dst_x = element->plane.x - output->x; + dst_y = element->plane.y - output->y; + + if (dst_x < 0) { + width += dst_x; + src_x -= dst_x; + dst_x = 0; + } + + if (dst_y < 0) { + height += dst_y; + src_y -= dst_y; + dst_y = 0; + } + + width = int_max(width, 0); + height = int_max(height, 0); + + /* src_rect is in 16.16, dst_rect is in 32.0 unsigned fixed point */ + vc_dispmanx_rect_set(src_rect, src_x << 16, src_y << 16, + width << 16, height << 16); + vc_dispmanx_rect_set(dst_rect, dst_x, dst_y, width, height); +} + +static void +rpi_element_dmx_add(struct rpi_element *element, + DISPMANX_UPDATE_HANDLE_T update, int layer) +{ + VC_DISPMANX_ALPHA_T alphasetup = { + DISPMANX_FLAGS_ALPHA_FROM_SOURCE | DISPMANX_FLAGS_ALPHA_PREMULT, + 255, /* opacity 0-255 */ + 0 /* mask resource handle */ + }; + VC_RECT_T dst_rect; + VC_RECT_T src_rect; + + rpi_element_compute_rects(element, &src_rect, &dst_rect); + + element->handle = vc_dispmanx_element_add( + update, + element->output->display, + layer, + &dst_rect, + element->back->handle, + &src_rect, + DISPMANX_PROTECTION_NONE, + &alphasetup, + NULL /* clamp */, + DISPMANX_NO_ROTATE); + DBG("element %p add %u\n", element, element->handle); +} + +static void +rpi_element_dmx_swap(struct rpi_element *element, + DISPMANX_UPDATE_HANDLE_T update) +{ + VC_RECT_T rect; + pixman_box32_t *r; + + /* XXX: skip, iff resource was not reallocated, and single-buffering */ + vc_dispmanx_element_change_source(update, element->handle, + element->back->handle); + + /* This is current damage now, after rpi_assign_plane() */ + r = pixman_region32_extents(&element->prev_damage); + + vc_dispmanx_rect_set(&rect, r->x1, r->y1, + r->x2 - r->x1, r->y2 - r->y1); + vc_dispmanx_element_modified(update, element->handle, &rect); + DBG("element %p swap\n", element); +} + +static void +rpi_element_dmx_move(struct rpi_element *element, + DISPMANX_UPDATE_HANDLE_T update, int layer) +{ + VC_RECT_T dst_rect; + VC_RECT_T src_rect; + + /* XXX: return early, if all attributes stay the same */ + + rpi_element_compute_rects(element, &src_rect, &dst_rect); + + vc_dispmanx_element_change_attributes( + update, + element->handle, + ELEMENT_CHANGE_LAYER | + ELEMENT_CHANGE_DEST_RECT | + ELEMENT_CHANGE_SRC_RECT, + layer, + 255, + &dst_rect, + &src_rect, + DISPMANX_NO_HANDLE, + DISPMANX_NO_ROTATE); + DBG("element %p move\n", element); +} + +static int +rpi_element_update(struct rpi_element *element, + DISPMANX_UPDATE_HANDLE_T update, int layer) +{ + struct rpi_resource *tmp; + + if (element->handle == DISPMANX_NO_HANDLE) { + /* need_swap is already true, see rpi_assign_plane() */ + + rpi_element_dmx_add(element, update, layer); + if (element->handle == DISPMANX_NO_HANDLE) + weston_log("ERROR rpi: element_add() failed.\n"); + } else { + if (element->need_swap) + rpi_element_dmx_swap(element, update); + rpi_element_dmx_move(element, update, layer); + } + element->layer = layer; + + if (element->need_swap) { + tmp = element->front; + element->front = element->back; + element->back = tmp; + element->need_swap = 0; + DBG("new back %p, new front %p\n", + element->back, element->front); + } + + return 0; +} + +static void +rpi_flippipe_update_complete(DISPMANX_UPDATE_HANDLE_T update, void *data) +{ + /* This function runs in a different thread. */ + struct rpi_flippipe *flippipe = data; + struct timeval tv; + uint64_t time; + ssize_t ret; + + /* manufacture flip completion timestamp */ + /* XXX: use CLOCK_MONOTONIC instead? */ + gettimeofday(&tv, NULL); + time = (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; + + ret = write(flippipe->writefd, &time, sizeof time); + if (ret != sizeof time) + weston_log("ERROR: %s failed to write, ret %zd, errno %d\n", + __func__, ret, errno); +} + +static int +rpi_dispmanx_update_submit(DISPMANX_UPDATE_HANDLE_T update, + struct rpi_output *output) +{ + /* + * The callback registered here will eventually be called + * in a different thread context. Therefore we cannot call + * the usual functions from rpi_flippipe_update_complete(). + * Instead, we have a pipe for passing the message from the + * thread, waking up the Weston main event loop, calling + * rpi_flippipe_handler(), and then ending up in + * rpi_output_update_complete() in the main thread context, + * where we can do the frame finishing work. + */ + return vc_dispmanx_update_submit(update, rpi_flippipe_update_complete, + &output->flippipe); +} + +static void +rpi_output_update_complete(struct rpi_output *output, uint64_t time); + +static int +rpi_flippipe_handler(int fd, uint32_t mask, void *data) { struct rpi_output *output = data; + ssize_t ret; + uint64_t time; + + if (mask != WL_EVENT_READABLE) + weston_log("ERROR: unexpected mask 0x%x in %s\n", + mask, __func__); + + ret = read(fd, &time, sizeof time); + if (ret != sizeof time) { + weston_log("ERROR: %s failed to read, ret %zd, errno %d\n", + __func__, ret, errno); + } + + rpi_output_update_complete(output, time); - weston_output_finish_frame(&output->base, - weston_compositor_get_time()); return 1; } +static int +rpi_flippipe_init(struct rpi_flippipe *flippipe, struct rpi_output *output) +{ + struct wl_event_loop *loop; + int fd[2]; + + if (pipe2(fd, O_CLOEXEC) == -1) + return -1; + + flippipe->readfd = fd[0]; + flippipe->writefd = fd[1]; + + loop = wl_display_get_event_loop(output->compositor->base.wl_display); + flippipe->source = wl_event_loop_add_fd(loop, flippipe->readfd, + WL_EVENT_READABLE, + rpi_flippipe_handler, output); + + if (!flippipe->source) { + close(flippipe->readfd); + close(flippipe->writefd); + return -1; + } + + return 0; +} + +static void +rpi_flippipe_release(struct rpi_flippipe *flippipe) +{ + wl_event_source_remove(flippipe->source); + close(flippipe->readfd); + close(flippipe->writefd); +} + +static struct rpi_element * +find_rpi_element_from_surface(struct weston_surface *surface) +{ + struct wl_listener *listener; + struct rpi_element *element; + + listener = wl_signal_get(&surface->surface.resource.destroy_signal, + rpi_element_handle_surface_destroy); + if (!listener) + return NULL; + + element = container_of(listener, struct rpi_element, + surface_destroy_listener); + + if (element->surface != surface) + weston_log("ERROR rpi: sanity check failure in %s.\n", + __func__); + + return element; +} + +static struct rpi_element * +rpi_assign_plane(struct weston_surface *surface, struct rpi_output *output) +{ + struct rpi_element *element; + + /* dispmanx elements cannot transform */ + if (surface->transform.enabled) { + /* XXX: inspect the transformation matrix, we might still + * be able to put it into an element; scaling, additional + * translation (window titlebar context menus?) + */ + DBG("surface %p rejected: transform\n", surface); + return NULL; + } + + /* only shm surfaces supported */ + if (surface->buffer && !wl_buffer_is_shm(surface->buffer)) { + DBG("surface %p rejected: not shm\n", surface); + return NULL; + } + + /* check if this surface previously belonged to an element */ + element = find_rpi_element_from_surface(surface); + + if (element) { + rpi_element_reuse(element); + element->plane.x = floor(surface->geometry.x); + element->plane.y = floor(surface->geometry.y); + DBG("surface %p reuse element %p\n", surface, element); + } else { + if (!surface->buffer) { + DBG("surface %p rejected: no buffer\n", surface); + return NULL; + } + + element = rpi_element_create(output, surface); + DBG("element %p created\n", element); + } + + if (!element) { + DBG("surface %p rejected: no element\n", surface); + return NULL; + } + + return element; +} + +static void +rpi_output_assign_planes(struct weston_output *base) +{ + struct rpi_output *output = to_rpi_output(base); + struct rpi_compositor *compositor = output->compositor; + struct weston_surface *surface; + pixman_region32_t overlap; + pixman_region32_t surface_overlap; + struct rpi_element *element; + int n = 0; + + /* Construct the list of rpi_elements to be used into + * output->element_list, which is empty right now. + * Re-used elements are moved from old_element_list to + * element_list. */ + + DBG("%s\n", __func__); + + pixman_region32_init(&overlap); + wl_list_for_each(surface, &compositor->base.surface_list, link) { + pixman_region32_init(&surface_overlap); + pixman_region32_intersect(&surface_overlap, &overlap, + &surface->transform.boundingbox); + + element = NULL; + if (!pixman_region32_not_empty(&surface_overlap) && + n < compositor->max_planes) + element = rpi_assign_plane(surface, output); + + if (element) { + weston_surface_move_to_plane(surface, &element->plane); + DBG("surface %p (%dx%d@%.1f,%.1f) to element %p\n", + surface, + surface->geometry.width, surface->geometry.height, + surface->geometry.x, surface->geometry.y, element); + + /* weston_surface_move_to_plane() does full-surface + * damage, if the plane is new, so no need to force + * initial resource update. + */ + if (rpi_element_damage(element, surface->buffer, + &surface->damage) < 0) { + rpi_element_schedule_destroy(element); + DBG("surface %p rejected: resource update failed\n", + surface); + element = NULL; + } else { + n++; + } + } + + if (!element) { + weston_surface_move_to_plane(surface, + &compositor->base.primary_plane); + DBG("surface %p (%dx%d@%.1f,%.1f) to primary plane\n", + surface, + surface->geometry.width, surface->geometry.height, + surface->geometry.x, surface->geometry.y); + pixman_region32_union(&overlap, &overlap, + &surface->transform.boundingbox); + } + + pixman_region32_fini(&surface_overlap); + } + pixman_region32_fini(&overlap); +} + +static void +rpi_remove_elements(struct wl_list *element_list, + DISPMANX_UPDATE_HANDLE_T update) +{ + struct rpi_element *element; + + wl_list_for_each(element, element_list, link) { + if (element->handle == DISPMANX_NO_HANDLE) + continue; + + vc_dispmanx_element_remove(update, element->handle); + DBG("element %p remove %u\n", element, element->handle); + element->handle = DISPMANX_NO_HANDLE; + } +} + +static void +rpi_output_destroy_old_elements(struct rpi_output *output) +{ + struct rpi_element *element, *tmp; + + wl_list_for_each_safe(element, tmp, &output->old_element_list, link) { + if (element->handle != DISPMANX_NO_HANDLE) + continue; + + rpi_element_destroy(element); + } +} + static void rpi_output_repaint(struct weston_output *base, pixman_region32_t *damage) { struct rpi_output *output = to_rpi_output(base); struct rpi_compositor *compositor = output->compositor; + struct rpi_element *element; + DISPMANX_UPDATE_HANDLE_T update; + int layer = 10000; + + DBG("%s\n", __func__); + + update = vc_dispmanx_update_start(0); + + /* update all live elements */ + wl_list_for_each(element, &output->element_list, link) { + if (rpi_element_update(element, update, layer--) < 0) + weston_log("ERROR rpi: element update failed.\n"); + } + + /* remove all unused elements */ + rpi_remove_elements(&output->old_element_list, update); + + /* schedule callback to rpi_output_update_complete() */ + rpi_dispmanx_update_submit(update, output); + /* XXX: if there is anything to composite in GL, + * framerate seems to suffer */ + /* XXX: optimise the renderer for the case of nothing to render */ + /* XXX: if nothing to render, remove the element... + * but how, is destroying the EGLSurface a bad performance hit? + */ compositor->base.renderer->repaint_output(&output->base, damage); - /* FIXME: hook into page flip done */ - wl_event_source_timer_update(output->finish_frame_timer, 10); + /* Move the list of elements into the old_element_list. */ + wl_list_insert_list(&output->old_element_list, &output->element_list); + wl_list_init(&output->element_list); +} + +static void +rpi_output_update_complete(struct rpi_output *output, uint64_t time) +{ + rpi_output_destroy_old_elements(output); + weston_output_finish_frame(&output->base, time); } static void @@ -158,20 +958,30 @@ rpi_output_destroy(struct weston_output *base) { struct rpi_output *output = to_rpi_output(base); DISPMANX_UPDATE_HANDLE_T update; + struct rpi_element *element, *tmp; - wl_event_source_remove(output->finish_frame_timer); + DBG("%s\n", __func__); + + rpi_flippipe_release(&output->flippipe); + + update = vc_dispmanx_update_start(0); + rpi_remove_elements(&output->element_list, update); + rpi_remove_elements(&output->old_element_list, update); + vc_dispmanx_element_remove(update, output->egl_element); + vc_dispmanx_update_submit_sync(update); eglDestroySurface(output->compositor->base.egl_display, output->base.egl_surface); + wl_list_for_each_safe(element, tmp, &output->element_list, link) + rpi_element_destroy(element); + + wl_list_for_each_safe(element, tmp, &output->old_element_list, link) + rpi_element_destroy(element); + wl_list_remove(&output->base.link); weston_output_destroy(&output->base); - update = vc_dispmanx_update_start(0); - vc_dispmanx_element_remove(update, output->egl_element); - vc_dispmanx_update_submit_sync(update); - - /* XXX: how to destroy Dispmanx objects? */ vc_dispmanx_display_close(output->display); free(output); @@ -187,23 +997,30 @@ rpi_output_create(struct rpi_compositor *compositor) VC_RECT_T src_rect; int ret; float mm_width, mm_height; - VC_DISPMANX_ALPHA_T alpharules = { + VC_DISPMANX_ALPHA_T alphasetup = { DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS, 255, /* opacity 0-255 */ 0 /* mask resource handle */ }; - struct wl_event_loop *loop; output = calloc(1, sizeof *output); if (!output) return -1; output->compositor = compositor; + output->single_buffer = compositor->single_buffer; + wl_list_init(&output->element_list); + wl_list_init(&output->old_element_list); + + if (rpi_flippipe_init(&output->flippipe, output) < 0) { + weston_log("Creating message pipe failed.\n"); + goto out_free; + } output->display = vc_dispmanx_display_open(DISPMANX_ID_HDMI); if (!output->display) { - weston_log("Failed to open dispmanx HDMI display."); - goto out_free; + weston_log("Failed to open dispmanx HDMI display.\n"); + goto out_pipe; } ret = vc_dispmanx_display_get_info(output->display, &modeinfo); @@ -218,15 +1035,15 @@ rpi_output_create(struct rpi_compositor *compositor) update = vc_dispmanx_update_start(0); output->egl_element = vc_dispmanx_element_add(update, - output->display, - 0 /* layer */, - &dst_rect, - 0 /* src resource */, - &src_rect, - DISPMANX_PROTECTION_NONE, - &alpharules, - NULL /* clamp */, - DISPMANX_NO_ROTATE); + output->display, + 0 /* layer */, + &dst_rect, + 0 /* src resource */, + &src_rect, + DISPMANX_PROTECTION_NONE, + &alphasetup, + NULL /* clamp */, + DISPMANX_NO_ROTATE); vc_dispmanx_update_submit_sync(update); output->egl_window.element = output->egl_element; @@ -254,7 +1071,8 @@ rpi_output_create(struct rpi_compositor *compositor) output->base.repaint = rpi_output_repaint; output->base.destroy = rpi_output_destroy; - output->base.assign_planes = NULL; + if (compositor->max_planes > 0) + output->base.assign_planes = rpi_output_assign_planes; output->base.set_backlight = NULL; output->base.set_dpms = NULL; output->base.switch_mode = NULL; @@ -288,10 +1106,6 @@ rpi_output_create(struct rpi_compositor *compositor) WL_OUTPUT_TRANSFORM_NORMAL); wl_list_insert(compositor->base.output_list.prev, &output->base.link); - loop = wl_display_get_event_loop(compositor->base.wl_display); - output->finish_frame_timer = - wl_event_loop_add_timer(loop, rpi_finish_frame, output); - weston_log("Raspberry Pi HDMI output %dx%d px\n", output->mode.width, output->mode.height); weston_log_continue(STAMP_SPACE "guessing %d Hz and 96 dpi\n", @@ -304,11 +1118,16 @@ out_surface: output->base.egl_surface); out_dmx: - /* XXX: how to destroy Dispmanx objects? */ + update = vc_dispmanx_update_start(0); + vc_dispmanx_element_remove(update, output->egl_element); + vc_dispmanx_update_submit_sync(update); out_dmx_close: vc_dispmanx_display_close(output->display); +out_pipe: + rpi_flippipe_release(&output->flippipe); + out_free: free(output); return -1; @@ -682,9 +1501,15 @@ switch_vt_binding(struct wl_seat *seat, uint32_t time, uint32_t key, void *data) tty_activate_vt(ec->tty, key - KEY_F1 + 1); } +struct rpi_parameters { + int tty; + int max_planes; + int single_buffer; +}; + static struct weston_compositor * rpi_compositor_create(struct wl_display *display, int argc, char *argv[], - const char *config_file, int tty) + const char *config_file, struct rpi_parameters *param) { struct rpi_compositor *compositor; struct weston_output *output; @@ -707,7 +1532,7 @@ rpi_compositor_create(struct wl_display *display, int argc, char *argv[], goto out_compositor; } - compositor->tty = tty_create(&compositor->base, vt_func, tty); + compositor->tty = tty_create(&compositor->base, vt_func, param->tty); if (!compositor->tty) { weston_log("Failed to initialize tty.\n"); goto out_udev; @@ -718,12 +1543,26 @@ rpi_compositor_create(struct wl_display *display, int argc, char *argv[], compositor->base.focus = 1; compositor->prev_state = WESTON_COMPOSITOR_ACTIVE; + compositor->max_planes = int_max(param->max_planes, 0); + compositor->single_buffer = param->single_buffer; + + weston_log("Maximum number of additional Dispmanx planes: %d\n", + compositor->max_planes); + weston_log("Dispmanx planes are %s buffered.\n", + compositor->single_buffer ? "single" : "double"); for (key = KEY_F1; key < KEY_F9; key++) weston_compositor_add_key_binding(&compositor->base, key, MODIFIER_CTRL | MODIFIER_ALT, switch_vt_binding, compositor); + /* + * bcm_host_init() creates threads. + * Therefore we must have all signal handlers set and signals blocked + * before calling it. Otherwise the signals may end in the bcm + * threads and cause the default behaviour there. For instance, + * SIGUSR1 used for VT switching caused Weston to terminate there. + */ bcm_host_init(); if (rpi_init_egl(compositor) < 0) @@ -762,17 +1601,40 @@ out_free: return NULL; } +/* + * If you have a recent enough firmware in Raspberry Pi, that + * supports falling back to off-line hardware compositing, and + * you have enabled it with dispmanx_offline=1 in /boot/config.txt, + * then VideoCore should be able to handle almost 100 Dispmanx + * elements. Therefore use 80 as the default limit. + * + * If you don't have off-line compositing support, this would be + * better as something like 10. Failing on-line compositing may + * show up as visible glitches, HDMI blanking, or invisible surfaces. + * + * When the max-planes number is reached, rpi-backend will start + * to fall back to GLESv2 compositing. + */ +#define DEFAULT_MAX_PLANES 80 + WL_EXPORT struct weston_compositor * backend_init(struct wl_display *display, int argc, char *argv[], const char *config_file) { - int tty = 0; /* default to current tty */ + struct rpi_parameters param = { + .tty = 0, /* default to current tty */ + .max_planes = DEFAULT_MAX_PLANES, + .single_buffer = 0, + }; const struct weston_option rpi_options[] = { - { WESTON_OPTION_INTEGER, "tty", 0, &tty }, + { WESTON_OPTION_INTEGER, "tty", 0, ¶m.tty }, + { WESTON_OPTION_INTEGER, "max-planes", 0, ¶m.max_planes }, + { WESTON_OPTION_BOOLEAN, "single-buffer", 0, + ¶m.single_buffer }, }; parse_options(rpi_options, ARRAY_LENGTH(rpi_options), argc, argv); - return rpi_compositor_create(display, argc, argv, config_file, tty); + return rpi_compositor_create(display, argc, argv, config_file, ¶m); } diff --git a/src/rpi-bcm-stubs.h b/src/rpi-bcm-stubs.h index 3d41888d..989c99cb 100644 --- a/src/rpi-bcm-stubs.h +++ b/src/rpi-bcm-stubs.h @@ -63,6 +63,14 @@ typedef enum { VC_IMAGE_ROT0, } VC_IMAGE_TRANSFORM_T; +typedef enum +{ + VC_IMAGE_MIN = 0, + /* these are not the right values: */ + VC_IMAGE_ARGB8888, + VC_IMAGE_XRGB8888, +} VC_IMAGE_TYPE_T; + /* from /opt/vc/include/interface/vmcs_host/vc_dispmanx_types.h */ typedef uint32_t DISPMANX_DISPLAY_HANDLE_T; @@ -107,6 +115,9 @@ typedef struct { uint32_t dummy; } DISPMANX_CLAMP_T; +typedef void (*DISPMANX_CALLBACK_FUNC_T)(DISPMANX_UPDATE_HANDLE_T u, + void *arg); + /* from /opt/vc/include/interface/vmcs_host/vc_dispmanx.h */ static inline int @@ -120,10 +131,45 @@ vc_dispmanx_rect_set(VC_RECT_T *rect, uint32_t x_offset, uint32_t y_offset, return 0; } +static inline DISPMANX_RESOURCE_HANDLE_T +vc_dispmanx_resource_create(VC_IMAGE_TYPE_T type, uint32_t width, + uint32_t height, uint32_t *native_image_handle) +{ + return DISPMANX_NO_HANDLE; +} + +static inline int +vc_dispmanx_resource_write_data(DISPMANX_RESOURCE_HANDLE_T res, + VC_IMAGE_TYPE_T src_type, + int src_pitch, + void *src_address, + const VC_RECT_T *rect) +{ + return -1; +} + +static inline int +vc_dispmanx_resource_write_data_rect(DISPMANX_RESOURCE_HANDLE_T handle, + VC_IMAGE_TYPE_T src_type, + int src_pitch, + void *src_address, + const VC_RECT_T *src_rect, + uint32_t dst_x, + uint32_t dst_y) +{ + return -1; +} + +static inline int +vc_dispmanx_resource_delete(DISPMANX_RESOURCE_HANDLE_T res) +{ + return -1; +} + static inline DISPMANX_DISPLAY_HANDLE_T vc_dispmanx_display_open(uint32_t device) { - return -1; + return DISPMANX_NO_HANDLE; } static inline int @@ -142,7 +188,7 @@ vc_dispmanx_display_get_info(DISPMANX_DISPLAY_HANDLE_T display, static inline DISPMANX_UPDATE_HANDLE_T vc_dispmanx_update_start(int32_t priority) { - return -1; + return DISPMANX_NO_HANDLE; } static inline DISPMANX_ELEMENT_HANDLE_T @@ -156,6 +202,22 @@ vc_dispmanx_element_add(DISPMANX_UPDATE_HANDLE_T update, VC_DISPMANX_ALPHA_T *alpha, DISPMANX_CLAMP_T *clamp, DISPMANX_TRANSFORM_T transform) +{ + return DISPMANX_NO_HANDLE; +} + +static inline int +vc_dispmanx_element_change_source(DISPMANX_UPDATE_HANDLE_T update, + DISPMANX_ELEMENT_HANDLE_T element, + DISPMANX_RESOURCE_HANDLE_T src) +{ + return -1; +} + +static inline int +vc_dispmanx_element_modified(DISPMANX_UPDATE_HANDLE_T update, + DISPMANX_ELEMENT_HANDLE_T element, + const VC_RECT_T *rect) { return -1; } @@ -167,12 +229,33 @@ vc_dispmanx_element_remove(DISPMANX_UPDATE_HANDLE_T update, return -1; } +static inline int +vc_dispmanx_update_submit(DISPMANX_UPDATE_HANDLE_T update, + DISPMANX_CALLBACK_FUNC_T cb_func, void *cb_arg) +{ + return -1; +} + static inline int vc_dispmanx_update_submit_sync(DISPMANX_UPDATE_HANDLE_T update) { return -1; } +static inline int +vc_dispmanx_element_change_attributes(DISPMANX_UPDATE_HANDLE_T update, + DISPMANX_ELEMENT_HANDLE_T element, + uint32_t change_flags, + int32_t layer, + uint8_t opacity, + const VC_RECT_T *dest_rect, + const VC_RECT_T *src_rect, + DISPMANX_RESOURCE_HANDLE_T mask, + VC_IMAGE_TRANSFORM_T transform) +{ + return -1; +} + /* from /opt/vc/include/EGL/eglplatform.h */ typedef struct {