diff --git a/src/Makefile.am b/src/Makefile.am index 859f5834..106ccba4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -180,6 +180,8 @@ rpi_backend_la_CFLAGS = \ $(RPI_BCM_HOST_CFLAGS) rpi_backend_la_SOURCES = \ compositor-rpi.c \ + rpi-renderer.c \ + rpi-renderer.h \ rpi-bcm-stubs.h \ tty.c \ evdev.c \ diff --git a/src/rpi-bcm-stubs.h b/src/rpi-bcm-stubs.h index 989c99cb..4b89319f 100644 --- a/src/rpi-bcm-stubs.h +++ b/src/rpi-bcm-stubs.h @@ -43,6 +43,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. static inline void bcm_host_init(void) {} static inline void bcm_host_deinit(void) {} + +/* from /opt/vc/include/interface/vmcs_host/vc_dispservice_defs.h */ + +#define TRANSFORM_HFLIP (1<<0) +#define TRANSFORM_VFLIP (1<<1) +#define TRANSFORM_TRANSPOSE (1<<2) + + /* from /opt/vc/include/interface/vctypes/vc_display_types.h */ typedef enum @@ -61,6 +69,14 @@ typedef struct tag_VC_RECT_T { typedef enum { VC_IMAGE_ROT0, + /* these are not the right values: */ + VC_IMAGE_ROT90, + VC_IMAGE_ROT180, + VC_IMAGE_ROT270, + VC_IMAGE_MIRROR_ROT0, + VC_IMAGE_MIRROR_ROT90, + VC_IMAGE_MIRROR_ROT180, + VC_IMAGE_MIRROR_ROT270, } VC_IMAGE_TRANSFORM_T; typedef enum @@ -109,6 +125,12 @@ typedef struct { typedef enum { DISPMANX_NO_ROTATE = 0, + DISPMANX_ROTATE_90 = 1, + DISPMANX_ROTATE_180 = 2, + DISPMANX_ROTATE_270 = 3, + + DISPMANX_FLIP_HRIZ = 1 << 16, + DISPMANX_FLIP_VERT = 1 << 17 } DISPMANX_TRANSFORM_T; typedef struct { @@ -160,6 +182,15 @@ vc_dispmanx_resource_write_data_rect(DISPMANX_RESOURCE_HANDLE_T handle, return -1; } +static inline int +vc_dispmanx_resource_read_data(DISPMANX_RESOURCE_HANDLE_T handle, + const VC_RECT_T *p_rect, + void *dst_address, + uint32_t dst_pitch) +{ + return -1; +} + static inline int vc_dispmanx_resource_delete(DISPMANX_RESOURCE_HANDLE_T res) { @@ -256,6 +287,14 @@ vc_dispmanx_element_change_attributes(DISPMANX_UPDATE_HANDLE_T update, return -1; } +static inline int +vc_dispmanx_snapshot(DISPMANX_DISPLAY_HANDLE_T display, + DISPMANX_RESOURCE_HANDLE_T snapshot_resource, + VC_IMAGE_TRANSFORM_T transform) +{ + return -1; +} + /* from /opt/vc/include/EGL/eglplatform.h */ typedef struct { diff --git a/src/rpi-renderer.c b/src/rpi-renderer.c new file mode 100644 index 00000000..e429d2ef --- /dev/null +++ b/src/rpi-renderer.c @@ -0,0 +1,1370 @@ +/* + * Copyright © 2012-2013 Raspberry Pi Foundation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include "config.h" + +#ifdef HAVE_BCM_HOST +# include +#else +# include "rpi-bcm-stubs.h" +#endif + +#include "compositor.h" +#include "rpi-renderer.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. While a + * dispmanx update is running, the both resources must be considered in use. + * + * A resource may be destroyed only, when the update removing the element has + * completed. Otherwise you risk showing an incomplete composition. + */ + +#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 + +#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_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 rpir_output; + +struct rpir_surface { + struct weston_surface *surface; + + /* If link is empty, the surface is guaranteed to not be on screen, + * i.e. updates removing Elements have completed. + */ + struct wl_list link; + + 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_buffer_reference buffer_ref; +}; + +struct rpir_output { + DISPMANX_DISPLAY_HANDLE_T display; + + DISPMANX_UPDATE_HANDLE_T update; + struct weston_matrix matrix; + + /* all Elements currently on screen */ + struct wl_list surface_list; /* struct rpir_surface::link */ + + /* Elements just removed, waiting for update completion */ + struct wl_list surface_cleanup_list; /* struct rpir_surface::link */ + + struct rpi_resource capture_buffer; + uint8_t *capture_data; +}; + +struct rpi_renderer { + struct weston_renderer base; + + int single_buffer; +}; + +static inline struct rpir_surface * +to_rpir_surface(struct weston_surface *surface) +{ + return surface->renderer_state; +} + +static inline struct rpir_output * +to_rpir_output(struct weston_output *output) +{ + return output->renderer_state; +} + +static inline struct rpi_renderer * +to_rpi_renderer(struct weston_compositor *compositor) +{ + return container_of(compositor->renderer, struct rpi_renderer, base); +} + +static inline int +int_max(int a, int b) +{ + return a > b ? a : b; +} + +static inline void +int_swap(int *a, int *b) +{ + int tmp = *a; + *a = *b; + *b = tmp; +} + +static uint8_t +float2uint8(float f) +{ + int v = roundf(f * 255.0f); + + return v < 0 ? 0 : (v > 255 ? 255 : v); +} + +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_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 1; +} + +/* A firmware workaround for broken ALPHA_PREMULT + ALPHA_MIX hardware. */ +#define PREMULT_ALPHA_FLAG (1 << 31) + +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 | PREMULT_ALPHA_FLAG; + 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); + + ret = rpi_resource_realloc(resource, ifmt & ~PREMULT_ALPHA_FLAG, + width, height, stride, height); + if (ret < 0) + return -1; + + pixman_region32_init_rect(&write_region, 0, 0, width, height); + if (ret == 0) + pixman_region32_intersect(&write_region, + &write_region, region); + +#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 struct rpir_surface * +rpir_surface_create(struct rpi_renderer *renderer) +{ + struct rpir_surface *surface; + + surface = calloc(1, sizeof *surface); + if (!surface) + return NULL; + + wl_list_init(&surface->link); + surface->single_buffer = renderer->single_buffer; + surface->handle = DISPMANX_NO_HANDLE; + rpi_resource_init(&surface->resources[0]); + rpi_resource_init(&surface->resources[1]); + surface->front = &surface->resources[0]; + if (surface->single_buffer) + surface->back = &surface->resources[0]; + else + surface->back = &surface->resources[1]; + + pixman_region32_init(&surface->prev_damage); + + return surface; +} + +static void +rpir_surface_destroy(struct rpir_surface *surface) +{ + wl_list_remove(&surface->link); + + if (surface->handle != DISPMANX_NO_HANDLE) + weston_log("ERROR rpi: destroying on-screen element\n"); + + pixman_region32_fini(&surface->prev_damage); + rpi_resource_release(&surface->resources[0]); + rpi_resource_release(&surface->resources[1]); + DBG("rpir_surface %p destroyed (%u)\n", surface, surface->handle); + + free(surface); +} + +static int +rpir_surface_damage(struct rpir_surface *surface, struct wl_buffer *buffer, + pixman_region32_t *damage) +{ + pixman_region32_t upload; + int ret; + + if (!pixman_region32_not_empty(damage)) + return 0; + + DBG("rpir_surface %p update resource %p\n", surface, surface->back); + + /* XXX: todo: if no surface->handle, update front buffer directly + * to avoid creating a new back buffer */ + if (surface->single_buffer) { + ret = rpi_resource_update(surface->front, buffer, damage); + } else { + pixman_region32_init(&upload); + pixman_region32_union(&upload, &surface->prev_damage, damage); + ret = rpi_resource_update(surface->back, buffer, &upload); + pixman_region32_fini(&upload); + } + + pixman_region32_copy(&surface->prev_damage, damage); + surface->need_swap = 1; + + return ret; +} + +static void +matrix_type_str(struct weston_matrix *matrix, char *buf, int len) +{ + static const char types[33] = "TSRO"; + unsigned mask = matrix->type; + int i = 0; + + while (mask && i < len - 1) { + if (mask & (1u << i)) + *buf++ = types[i]; + mask &= ~(1u << i); + i++; + } + *buf = '\0'; +} + +static void +log_print_matrix(struct weston_matrix *matrix) +{ + char typestr[6]; + float *d = matrix->d; + + matrix_type_str(matrix, typestr, sizeof typestr); + weston_log_continue("%14.6e %14.6e %14.6e %14.6e\n", + d[0], d[4], d[8], d[12]); + weston_log_continue("%14.6e %14.6e %14.6e %14.6e\n", + d[1], d[5], d[9], d[13]); + weston_log_continue("%14.6e %14.6e %14.6e %14.6e\n", + d[2], d[6], d[10], d[14]); + weston_log_continue("%14.6e %14.6e %14.6e %14.6e type: %s\n", + d[3], d[7], d[11], d[15], typestr); +} + +static void +warn_bad_matrix(struct weston_matrix *total, struct weston_matrix *output, + struct weston_matrix *surface) +{ + static int n_warn; + char typestr[6]; + + if (n_warn++ == 10) + weston_log("%s: not showing more warnings\n", __func__); + + if (n_warn > 10) + return; + + weston_log("%s: warning: total transformation is not renderable:\n", + __func__); + log_print_matrix(total); + + matrix_type_str(surface, typestr, sizeof typestr); + weston_log_continue("surface matrix type: %s\n", typestr); + matrix_type_str(output, typestr, sizeof typestr); + weston_log_continue("output matrix type: %s\n", typestr); +} + +/*#define SURFACE_TRANSFORM */ + +static int +rpir_surface_compute_rects(struct rpir_surface *surface, + VC_RECT_T *src_rect, VC_RECT_T *dst_rect, + VC_IMAGE_TRANSFORM_T *flipmask) +{ + struct weston_output *output_base = surface->surface->output; + struct rpir_output *output = to_rpir_output(output_base); + struct weston_matrix matrix = surface->surface->transform.matrix; + VC_IMAGE_TRANSFORM_T flipt = 0; + int src_x, src_y; + int dst_x, dst_y; + int src_width, src_height; + int dst_width, dst_height; + struct weston_vector p1 = {{ 0.0f, 0.0f, 0.0f, 1.0f }}; + struct weston_vector p2 = {{ 0.0f, 0.0f, 0.0f, 1.0f }}; + int t; + int over; + + /* XXX: take buffer transform into account */ + + /* src is in 16.16, dst is in 32.0 fixed point. + * Negative values are not allowed in VC_RECT_T. + * Clip size to output boundaries, firmware ignores + * huge elements like 8192x8192. + */ + + src_x = 0 << 16; + src_y = 0 << 16; + src_width = surface->front->width << 16; + src_height = surface->front->height << 16; + + weston_matrix_multiply(&matrix, &output->matrix); + +#ifdef SURFACE_TRANSFORM + if (matrix.type >= WESTON_MATRIX_TRANSFORM_OTHER) { +#else + if (matrix.type >= WESTON_MATRIX_TRANSFORM_ROTATE) { +#endif + warn_bad_matrix(&matrix, &output->matrix, + &surface->surface->transform.matrix); + } else { + if (matrix.type & WESTON_MATRIX_TRANSFORM_ROTATE) { + if (fabsf(matrix.d[0]) < 1e-4f && + fabsf(matrix.d[5]) < 1e-4f) { + flipt |= TRANSFORM_TRANSPOSE; + } else if (fabsf(matrix.d[1]) < 1e-4 && + fabsf(matrix.d[4]) < 1e-4) { + /* no transpose */ + } else { + warn_bad_matrix(&matrix, &output->matrix, + &surface->surface->transform.matrix); + } + } + } + + p2.f[0] = surface->surface->geometry.width; + p2.f[1] = surface->surface->geometry.height; + + /* transform top-left and bot-right corner into screen coordinates */ + weston_matrix_transform(&matrix, &p1); + weston_matrix_transform(&matrix, &p2); + + /* Compute the destination rectangle on screen, converting + * negative dimensions to flips. + */ + + dst_width = round(p2.f[0] - p1.f[0]); + if (dst_width < 0) { + dst_x = round(p2.f[0]); + dst_width = -dst_width; + + if (!(flipt & TRANSFORM_TRANSPOSE)) + flipt |= TRANSFORM_HFLIP; + else + flipt |= TRANSFORM_VFLIP; + } else { + dst_x = round(p1.f[0]); + } + + dst_height = round(p2.f[1] - p1.f[1]); + if (dst_height < 0) { + dst_y = round(p2.f[1]); + dst_height = -dst_height; + + if (!(flipt & TRANSFORM_TRANSPOSE)) + flipt |= TRANSFORM_VFLIP; + else + flipt |= TRANSFORM_HFLIP; + } else { + dst_y = round(p1.f[1]); + } + + if (dst_width == 0 || dst_height == 0) { + DBG("ignored, zero surface area before clipping\n"); + return -1; + } + +#ifdef SURFACE_TRANSFORM + /* Dispmanx works as if you flipped the whole screen, when + * you flip an element. But, we want to flip an element in place. + * XXX: fixme + */ + if (flipt & TRANSFORM_HFLIP) + dst_x = output_base->width - dst_x; + if (flipt & TRANSFORM_VFLIP) + dst_y = output_base->height - dst_y; + if (flipt & TRANSFORM_TRANSPOSE) { + int_swap(&dst_x, &dst_y); + int_swap(&dst_width, &dst_height); + } +#else + switch (output_base->transform) { + case WL_OUTPUT_TRANSFORM_FLIPPED: + flipt = TRANSFORM_HFLIP; + break; + case WL_OUTPUT_TRANSFORM_NORMAL: + flipt = 0; + break; + + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + flipt = TRANSFORM_HFLIP | TRANSFORM_VFLIP | TRANSFORM_TRANSPOSE; + break; + case WL_OUTPUT_TRANSFORM_90: + flipt = TRANSFORM_VFLIP | TRANSFORM_TRANSPOSE; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + flipt = TRANSFORM_VFLIP; + break; + case WL_OUTPUT_TRANSFORM_180: + flipt = TRANSFORM_HFLIP | TRANSFORM_VFLIP; + break; + + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + flipt = TRANSFORM_TRANSPOSE; + break; + case WL_OUTPUT_TRANSFORM_270: + flipt = TRANSFORM_HFLIP | TRANSFORM_TRANSPOSE; + break; + default: + break; + } +#endif + + /* clip destination rectangle to screen dimensions */ + + if (dst_x < 0) { + t = (int64_t)dst_x * src_width / dst_width; + src_width += t; + dst_width += dst_x; + src_x -= t; + dst_x = 0; + } + + if (dst_y < 0) { + t = (int64_t)dst_y * src_height / dst_height; + src_height += t; + dst_height += dst_y; + src_y -= t; + dst_y = 0; + } + + over = dst_x + dst_width - output_base->width; + if (over > 0) { + t = (int64_t)over * src_width / dst_width; + src_width -= t; + dst_width -= over; + } + + over = dst_y + dst_height - output_base->height; + if (over > 0) { + t = (int64_t)over * src_height / dst_height; + src_height -= t; + dst_height -= over; + } + + src_width = int_max(src_width, 0); + src_height = int_max(src_height, 0); + + DBG("rpir_surface %p %dx%d: p1 %f, %f; p2 %f, %f\n", surface, + surface->surface->geometry.width, + surface->surface->geometry.height, + p1.f[0], p1.f[1], p2.f[0], p2.f[1]); + DBG("src rect %d;%d, %d;%d, %d;%dx%d;%d\n", + src_x >> 16, src_x & 0xffff, + src_y >> 16, src_y & 0xffff, + src_width >> 16, src_width & 0xffff, + src_height >> 16, src_height & 0xffff); + DBG("dest rect %d, %d, %dx%d%s%s%s\n", + dst_x, dst_y, dst_width, dst_height, + (flipt & TRANSFORM_HFLIP) ? " hflip" : "", + (flipt & TRANSFORM_VFLIP) ? " vflip" : "", + (flipt & TRANSFORM_TRANSPOSE) ? " transp" : ""); + + assert(src_x >= 0); + assert(src_y >= 0); + assert(dst_x >= 0); + assert(dst_y >= 0); + + if (dst_width < 1 || dst_height < 1) { + DBG("ignored, zero surface area after clipping\n"); + return -1; + } + + vc_dispmanx_rect_set(src_rect, src_x, src_y, src_width, src_height); + vc_dispmanx_rect_set(dst_rect, dst_x, dst_y, dst_width, dst_height); + *flipmask = flipt; + + return 0; +} + +static DISPMANX_TRANSFORM_T +vc_image2dispmanx_transform(VC_IMAGE_TRANSFORM_T t) +{ + /* XXX: uhh, are these right? */ + switch (t) { + case VC_IMAGE_ROT0: + return DISPMANX_NO_ROTATE; + case VC_IMAGE_MIRROR_ROT0: + return DISPMANX_FLIP_HRIZ; + case VC_IMAGE_MIRROR_ROT180: + return DISPMANX_FLIP_VERT; + case VC_IMAGE_ROT180: + return DISPMANX_ROTATE_180; + case VC_IMAGE_MIRROR_ROT90: + return DISPMANX_ROTATE_90 | DISPMANX_FLIP_HRIZ; + case VC_IMAGE_ROT270: + return DISPMANX_ROTATE_270; + case VC_IMAGE_ROT90: + return DISPMANX_ROTATE_90; + case VC_IMAGE_MIRROR_ROT270: + return DISPMANX_ROTATE_270 | DISPMANX_FLIP_VERT; + default: + assert(0 && "bad VC_IMAGE_TRANSFORM_T"); + return DISPMANX_NO_ROTATE; + } +} + +static int +rpir_surface_dmx_add(struct rpir_surface *surface, struct rpir_output *output, + DISPMANX_UPDATE_HANDLE_T update, int layer) +{ + /* Do not use DISPMANX_FLAGS_ALPHA_PREMULT here. + * If you define PREMULT and ALPHA_MIX, the hardware will not + * multiply the source color with the element alpha, leading to + * bad colors. Instead, we define PREMULT during pixel data upload. + */ + VC_DISPMANX_ALPHA_T alphasetup = { + DISPMANX_FLAGS_ALPHA_FROM_SOURCE | + DISPMANX_FLAGS_ALPHA_MIX, + float2uint8(surface->surface->alpha), /* opacity 0-255 */ + 0 /* mask resource handle */ + }; + VC_RECT_T dst_rect; + VC_RECT_T src_rect; + VC_IMAGE_TRANSFORM_T flipmask; + int ret; + + ret = rpir_surface_compute_rects(surface, &src_rect, &dst_rect, + &flipmask); + if (ret < 0) + return 0; + + surface->handle = vc_dispmanx_element_add( + update, + output->display, + layer, + &dst_rect, + surface->front->handle, + &src_rect, + DISPMANX_PROTECTION_NONE, + &alphasetup, + NULL /* clamp */, + vc_image2dispmanx_transform(flipmask)); + DBG("rpir_surface %p add %u, alpha %f\n", surface, surface->handle, + surface->surface->alpha); + + if (surface->handle == DISPMANX_NO_HANDLE) + return -1; + + return 1; +} + +static void +rpir_surface_dmx_swap(struct rpir_surface *surface, + 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, surface->handle, + surface->front->handle); + + /* This is current damage now, after rpir_surface_damage() */ + r = pixman_region32_extents(&surface->prev_damage); + + vc_dispmanx_rect_set(&rect, r->x1, r->y1, + r->x2 - r->x1, r->y2 - r->y1); + vc_dispmanx_element_modified(update, surface->handle, &rect); + DBG("rpir_surface %p swap\n", surface); +} + +static int +rpir_surface_dmx_move(struct rpir_surface *surface, + DISPMANX_UPDATE_HANDLE_T update, int layer) +{ + uint8_t alpha = float2uint8(surface->surface->alpha); + VC_RECT_T dst_rect; + VC_RECT_T src_rect; + VC_IMAGE_TRANSFORM_T flipmask; + int ret; + + /* XXX: return early, if all attributes stay the same */ + + ret = rpir_surface_compute_rects(surface, &src_rect, &dst_rect, + &flipmask); + if (ret < 0) + return 0; + + ret = vc_dispmanx_element_change_attributes( + update, + surface->handle, + ELEMENT_CHANGE_LAYER | + ELEMENT_CHANGE_OPACITY | + ELEMENT_CHANGE_TRANSFORM | + ELEMENT_CHANGE_DEST_RECT | + ELEMENT_CHANGE_SRC_RECT, + layer, + alpha, + &dst_rect, + &src_rect, + DISPMANX_NO_HANDLE, + /* This really is DISPMANX_TRANSFORM_T, no matter + * what the header says. */ + vc_image2dispmanx_transform(flipmask)); + DBG("rpir_surface %p move\n", surface); + + if (ret) + return -1; + + return 1; +} + +static void +rpir_surface_dmx_remove(struct rpir_surface *surface, + DISPMANX_UPDATE_HANDLE_T update) +{ + if (surface->handle == DISPMANX_NO_HANDLE) + return; + + vc_dispmanx_element_remove(update, surface->handle); + DBG("rpir_surface %p remove %u\n", surface, surface->handle); + surface->handle = DISPMANX_NO_HANDLE; +} + +static void +rpir_surface_swap_pointers(struct rpir_surface *surface) +{ + struct rpi_resource *tmp; + + tmp = surface->front; + surface->front = surface->back; + surface->back = tmp; + surface->need_swap = 0; + DBG("new back %p, new front %p\n", surface->back, surface->front); +} + +static int +is_surface_not_visible(struct weston_surface *surface) +{ + /* Return true, if surface is guaranteed to be totally obscured. */ + int ret; + pixman_region32_t unocc; + + pixman_region32_init(&unocc); + pixman_region32_subtract(&unocc, &surface->transform.boundingbox, + &surface->clip); + ret = !pixman_region32_not_empty(&unocc); + pixman_region32_fini(&unocc); + + return ret; +} + +static void +rpir_surface_update(struct rpir_surface *surface, struct rpir_output *output, + DISPMANX_UPDATE_HANDLE_T update, int layer) +{ + int need_swap = surface->need_swap; + int ret; + int obscured; + + if (need_swap) + rpir_surface_swap_pointers(surface); + + obscured = is_surface_not_visible(surface->surface); + if (obscured) { + DBG("rpir_surface %p totally obscured.\n", surface); + + wl_list_remove(&surface->link); + if (surface->handle == DISPMANX_NO_HANDLE) { + wl_list_init(&surface->link); + } else { + rpir_surface_dmx_remove(surface, update); + wl_list_insert(&output->surface_cleanup_list, + &surface->link); + } + + goto out; + } + + if (surface->handle == DISPMANX_NO_HANDLE) { + ret = rpir_surface_dmx_add(surface, output, update, layer); + if (ret == 0) { + wl_list_remove(&surface->link); + wl_list_init(&surface->link); + } else if (ret < 0) { + weston_log("ERROR rpir_surface_dmx_add() failed.\n"); + } + } else { + if (need_swap) + rpir_surface_dmx_swap(surface, update); + + ret = rpir_surface_dmx_move(surface, update, layer); + if (ret == 0) { + rpir_surface_dmx_remove(surface, update); + + wl_list_remove(&surface->link); + wl_list_insert(&output->surface_cleanup_list, + &surface->link); + } else if (ret < 0) { + weston_log("ERROR rpir_surface_dmx_move() failed.\n"); + } + } + +out: + surface->layer = layer; +} + +static int +rpi_renderer_read_pixels(struct weston_output *base, + pixman_format_code_t format, void *pixels, + uint32_t x, uint32_t y, + uint32_t width, uint32_t height) +{ + struct rpir_output *output = to_rpir_output(base); + struct rpi_resource *buffer = &output->capture_buffer; + VC_RECT_T rect; + uint32_t fb_width, fb_height; + uint32_t dst_pitch; + uint32_t i; + int ret; + + fb_width = base->current->width; + fb_height = base->current->height; + + DBG("%s(%u, %u, %u, %u), resource %p\n", __func__, + x, y, width, height, buffer); + + if (format != PIXMAN_a8r8g8b8) { + weston_log("rpi-renderer error: bad read_format\n"); + return -1; + } + + dst_pitch = fb_width * 4; + + if (buffer->handle == DISPMANX_NO_HANDLE) { + free(output->capture_data); + output->capture_data = NULL; + + ret = rpi_resource_realloc(buffer, VC_IMAGE_ARGB8888, + fb_width, fb_height, + dst_pitch, fb_height); + if (ret < 0) { + weston_log("rpi-renderer error: " + "allocating read buffer failed\n"); + return -1; + } + + ret = vc_dispmanx_snapshot(output->display, buffer->handle, + VC_IMAGE_ROT0); + if (ret) { + weston_log("rpi-renderer error: " + "vc_dispmanx_snapshot returned %d\n", ret); + return -1; + } + DBG("%s: snapshot done.\n", __func__); + } + + /* + * If vc_dispmanx_resource_read_data was able to read sub-rectangles, + * we could read directly into 'pixels'. But it cannot, it does not + * use rect.x or rect.width, and does this: + * host_start = (uint8_t *)dst_address + (dst_pitch * p_rect->y); + * In other words, it is only good for reading the full buffer in + * one go. + */ + vc_dispmanx_rect_set(&rect, 0, 0, fb_width, fb_height); + + if (x == 0 && y == 0 && width == fb_width && height == fb_height) { + ret = vc_dispmanx_resource_read_data(buffer->handle, &rect, + pixels, dst_pitch); + if (ret) { + weston_log("rpi-renderer error: " + "resource_read_data returned %d\n", ret); + return -1; + } + DBG("%s: full frame done.\n", __func__); + return 0; + } + + if (!output->capture_data) { + output->capture_data = malloc(fb_height * dst_pitch); + if (!output->capture_data) { + weston_log("rpi-renderer error: " + "out of memory\n"); + return -1; + } + + ret = vc_dispmanx_resource_read_data(buffer->handle, &rect, + output->capture_data, + dst_pitch); + if (ret) { + weston_log("rpi-renderer error: " + "resource_read_data returned %d\n", ret); + return -1; + } + } + + for (i = 0; i < height; i++) { + uint8_t *src = output->capture_data + + (y + i) * dst_pitch + x * 4; + uint8_t *dst = (uint8_t *)pixels + i * width * 4; + memcpy(dst, src, width * 4); + } + + return 0; +} + +static void +rpir_output_dmx_remove_all(struct rpir_output *output, + DISPMANX_UPDATE_HANDLE_T update) +{ + struct rpir_surface *surface; + + while (!wl_list_empty(&output->surface_list)) { + surface = container_of(output->surface_list.next, + struct rpir_surface, link); + rpir_surface_dmx_remove(surface, update); + + wl_list_remove(&surface->link); + wl_list_insert(&output->surface_cleanup_list, &surface->link); + } +} + +static void +output_compute_matrix(struct weston_output *base) +{ + struct rpir_output *output = to_rpir_output(base); + struct weston_matrix *matrix = &output->matrix; + const float half_w = 0.5f * base->width; + const float half_h = 0.5f * base->height; + float mag; + float dx, dy; + + weston_matrix_init(matrix); + weston_matrix_translate(matrix, -base->x, -base->y, 0.0f); + +#ifdef SURFACE_TRANSFORM + weston_matrix_translate(matrix, -half_w, -half_h, 0.0f); + switch (base->transform) { + case WL_OUTPUT_TRANSFORM_FLIPPED: + weston_matrix_scale(matrix, -1.0f, 1.0f, 1.0f); + case WL_OUTPUT_TRANSFORM_NORMAL: + /* weston_matrix_rotate_xy(matrix, 1.0f, 0.0f); no-op */ + weston_matrix_translate(matrix, half_w, half_h, 0.0f); + break; + + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + weston_matrix_scale(matrix, -1.0f, 1.0f, 1.0f); + case WL_OUTPUT_TRANSFORM_90: + weston_matrix_rotate_xy(matrix, 0.0f, 1.0f); + weston_matrix_translate(matrix, half_h, half_w, 0.0f); + break; + + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + weston_matrix_scale(matrix, -1.0f, 1.0f, 1.0f); + case WL_OUTPUT_TRANSFORM_180: + weston_matrix_rotate_xy(matrix, -1.0f, 0.0f); + weston_matrix_translate(matrix, half_w, half_h, 0.0f); + break; + + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + weston_matrix_scale(matrix, -1.0f, 1.0f, 1.0f); + case WL_OUTPUT_TRANSFORM_270: + weston_matrix_rotate_xy(matrix, 0.0f, -1.0f); + weston_matrix_translate(matrix, half_h, half_w, 0.0f); + break; + + default: + break; + } +#endif + + if (base->zoom.active) { + /* The base->zoom stuff is in GL coordinate system */ + mag = 1.0f / (1.0f - base->zoom.spring_z.current); + dx = -(base->zoom.trans_x + 1.0f) * half_w; + dy = -(base->zoom.trans_y + 1.0f) * half_h; + weston_matrix_translate(matrix, dx, dy, 0.0f); + weston_matrix_scale(matrix, mag, mag, 1.0f); + weston_matrix_translate(matrix, half_w, half_h, 0.0f); + } +} + +/* Note: this won't work right for multiple outputs. A DispmanX Element + * is tied to one DispmanX Display, i.e. output. + */ +static void +rpi_renderer_repaint_output(struct weston_output *base, + pixman_region32_t *output_damage) +{ + struct weston_compositor *compositor = base->compositor; + struct rpir_output *output = to_rpir_output(base); + struct weston_surface *ws; + struct rpir_surface *surface; + struct wl_list done_list; + int layer = 1; + + assert(output->update != DISPMANX_NO_HANDLE); + + output_compute_matrix(base); + + rpi_resource_release(&output->capture_buffer); + free(output->capture_data); + output->capture_data = NULL; + + /* update all renderable surfaces */ + wl_list_init(&done_list); + wl_list_for_each_reverse(ws, &compositor->surface_list, link) { + if (ws->plane != &compositor->primary_plane) + continue; + + surface = to_rpir_surface(ws); + assert(!wl_list_empty(&surface->link) || + surface->handle == DISPMANX_NO_HANDLE); + + wl_list_remove(&surface->link); + wl_list_insert(&done_list, &surface->link); + rpir_surface_update(surface, output, output->update, layer++); + } + + /* Remove all surfaces that are still on screen, but were + * not rendered this time. + */ + rpir_output_dmx_remove_all(output, output->update); + + wl_list_insert_list(&output->surface_list, &done_list); + output->update = DISPMANX_NO_HANDLE; + + /* The frame_signal is emitted in rpi_renderer_finish_frame(), + * so that the firmware can capture the up-to-date contents. + */ +} + +static void +rpi_renderer_flush_damage(struct weston_surface *base) +{ + /* Called for every surface just before repainting it, if + * having an shm buffer. + */ + struct rpir_surface *surface = to_rpir_surface(base); + struct wl_buffer *buffer = surface->buffer_ref.buffer; + int ret; + + assert(buffer); + assert(wl_buffer_is_shm(buffer)); + + ret = rpir_surface_damage(surface, buffer, &base->damage); + if (ret) + weston_log("%s error: updating Dispmanx resource failed.\n", + __func__); + + weston_buffer_reference(&surface->buffer_ref, NULL); +} + +static void +rpi_renderer_attach(struct weston_surface *base, struct wl_buffer *buffer) +{ + /* Called every time a client commits an attach. */ + static int warned; + struct rpir_surface *surface = to_rpir_surface(base); + + assert(surface); + if (!surface) + return; + + if (buffer && !wl_buffer_is_shm(buffer) && !warned) { + weston_log("Error: non-wl_shm buffers not supported.\n"); + warned = 1; + return; + } + + weston_buffer_reference(&surface->buffer_ref, buffer); + + /* XXX: need to check if in middle of update + if (!buffer && !surface->single_buffer) + rpi_resource_release(surface->back); */ + + /* XXX: cannot do this, if middle of an update + if (surface->handle == DISPMANX_NO_HANDLE) + rpi_resource_release(surface->front); */ + + /* If buffer is NULL, Weston core unmaps the surface, the surface + * will not appear in repaint list, and so rpi_renderer_repaint_output + * will remove the DispmanX element. Later, also the front buffer + * will be released in the cleanup_list processing. + */ +} + +static int +rpi_renderer_create_surface(struct weston_surface *base) +{ + struct rpi_renderer *renderer = to_rpi_renderer(base->compositor); + struct rpir_surface *surface; + + assert(base->renderer_state == NULL); + + surface = rpir_surface_create(renderer); + if (!surface) + return -1; + + surface->surface = base; + base->renderer_state = surface; + return 0; +} + +static void +rpi_renderer_surface_set_color(struct weston_surface *base, + float red, float green, float blue, float alpha) +{ + struct rpir_surface *surface = to_rpir_surface(base); + uint8_t color[4]; + VC_RECT_T rect; + int ret; + + assert(surface); + + ret = rpi_resource_realloc(surface->back, VC_IMAGE_ARGB8888, + 1, 1, 4, 1); + if (ret < 0) { + weston_log("Error: %s: rpi_resource_realloc failed.\n", + __func__); + return; + } + + color[0] = float2uint8(blue); + color[1] = float2uint8(green); + color[2] = float2uint8(red); + color[3] = float2uint8(alpha); + + vc_dispmanx_rect_set(&rect, 0, 0, 1, 1); + ret = vc_dispmanx_resource_write_data(surface->back->handle, + VC_IMAGE_ARGB8888, + 4, color, &rect); + if (ret) { + weston_log("Error: %s: resource_write_data failed.\n", + __func__); + return; + } + + DBG("%s: resource %p solid color BGRA %u,%u,%u,%u\n", __func__, + surface->back, color[0], color[1], color[2], color[3]); + + /*pixman_region32_copy(&surface->prev_damage, damage);*/ + surface->need_swap = 1; +} + +static void +rpi_renderer_destroy_surface(struct weston_surface *base) +{ + struct rpir_surface *surface = to_rpir_surface(base); + + assert(surface); + assert(surface->surface == base); + if (!surface) + return; + + surface->surface = NULL; + base->renderer_state = NULL; + + /* If guaranteed to not be on screen, just detroy it. */ + if (wl_list_empty(&surface->link)) + rpir_surface_destroy(surface); + + /* Otherwise, the surface is either on screen and needs + * to be removed by a repaint update, or it is in the + * surface_cleanup_list, and will be destroyed by + * rpi_renderer_finish_frame(). + */ +} + +static void +rpi_renderer_destroy(struct weston_compositor *compositor) +{ + struct rpi_renderer *renderer = to_rpi_renderer(compositor); + + free(renderer); + compositor->renderer = NULL; +} + +WL_EXPORT int +rpi_renderer_create(struct weston_compositor *compositor, + const struct rpi_renderer_parameters *params) +{ + struct rpi_renderer *renderer; + + weston_log("Initializing the DispmanX compositing renderer\n"); + + renderer = calloc(1, sizeof *renderer); + if (renderer == NULL) + return -1; + + renderer->single_buffer = params->single_buffer; + + renderer->base.read_pixels = rpi_renderer_read_pixels; + renderer->base.repaint_output = rpi_renderer_repaint_output; + renderer->base.flush_damage = rpi_renderer_flush_damage; + renderer->base.attach = rpi_renderer_attach; + renderer->base.create_surface = rpi_renderer_create_surface; + renderer->base.surface_set_color = rpi_renderer_surface_set_color; + renderer->base.destroy_surface = rpi_renderer_destroy_surface; + renderer->base.destroy = rpi_renderer_destroy; + + compositor->renderer = &renderer->base; + compositor->read_format = PIXMAN_a8r8g8b8; + /* WESTON_CAP_ROTATION_ANY not supported */ + + return 0; +} + +WL_EXPORT int +rpi_renderer_output_create(struct weston_output *base, + DISPMANX_DISPLAY_HANDLE_T display) +{ + struct rpir_output *output; + + assert(base->renderer_state == NULL); + + output = calloc(1, sizeof *output); + if (!output) + return -1; + + output->display = display; + output->update = DISPMANX_NO_HANDLE; + wl_list_init(&output->surface_list); + wl_list_init(&output->surface_cleanup_list); + rpi_resource_init(&output->capture_buffer); + base->renderer_state = output; + + return 0; +} + +WL_EXPORT void +rpi_renderer_output_destroy(struct weston_output *base) +{ + struct rpir_output *output = to_rpir_output(base); + struct rpir_surface *surface; + DISPMANX_UPDATE_HANDLE_T update; + + rpi_resource_release(&output->capture_buffer); + free(output->capture_data); + output->capture_data = NULL; + + update = vc_dispmanx_update_start(0); + rpir_output_dmx_remove_all(output, update); + vc_dispmanx_update_submit_sync(update); + + while (!wl_list_empty(&output->surface_cleanup_list)) { + surface = container_of(output->surface_cleanup_list.next, + struct rpir_surface, link); + if (surface->surface) + surface->surface->renderer_state = NULL; + rpir_surface_destroy(surface); + } + + free(output); + base->renderer_state = NULL; +} + +WL_EXPORT void +rpi_renderer_set_update_handle(struct weston_output *base, + DISPMANX_UPDATE_HANDLE_T handle) +{ + struct rpir_output *output = to_rpir_output(base); + + output->update = handle; +} + +WL_EXPORT void +rpi_renderer_finish_frame(struct weston_output *base) +{ + struct rpir_output *output = to_rpir_output(base); + struct rpir_surface *surface; + + while (!wl_list_empty(&output->surface_cleanup_list)) { + surface = container_of(output->surface_cleanup_list.next, + struct rpir_surface, link); + + if (surface->surface) { + /* The weston_surface still exists, but is + * temporarily not visible, and hence its Element + * was removed. The current front buffer contents + * must be preserved. + */ + if (!surface->single_buffer) + rpi_resource_release(surface->back); + + wl_list_remove(&surface->link); + wl_list_init(&surface->link); + } else { + rpir_surface_destroy(surface); + } + } + + wl_signal_emit(&base->frame_signal, base); +} diff --git a/src/rpi-renderer.h b/src/rpi-renderer.h new file mode 100644 index 00000000..28ae3039 --- /dev/null +++ b/src/rpi-renderer.h @@ -0,0 +1,48 @@ +/* + * Copyright © 2013 Raspberry Pi Foundation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef RPI_RENDERER_H +#define RPI_RENDERER_H + +struct rpi_renderer_parameters { + int single_buffer; +}; + +int +rpi_renderer_create(struct weston_compositor *compositor, + const struct rpi_renderer_parameters *params); + +int +rpi_renderer_output_create(struct weston_output *base, + DISPMANX_DISPLAY_HANDLE_T display); + +void +rpi_renderer_output_destroy(struct weston_output *base); + +void +rpi_renderer_set_update_handle(struct weston_output *base, + DISPMANX_UPDATE_HANDLE_T handle); + +void +rpi_renderer_finish_frame(struct weston_output *base); + +#endif /* RPI_RENDERER_H */