You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
weston/src/rpi-renderer.c

1371 lines
36 KiB

rpi: add a Dispmanx renderer Dispmanx is the prorietary display API on the Raspberry Pi, which provides hardware compositing. Every visible surface is assigned a Dispmanx element, and the hardware or firmware will do all compositing onto screen. The API supports translation, scaling, flips, discrete rotations in 90-degree steps, alpha channel on the surfaces, and full-surface alpha on top. Previously, Dispmanx capabilities were used via the weston_plane mechanism, where surfaces were assigned to planes when possible, and otherwise transparently falling back to GLESv2 compositing. Because we have no way to use the same memory buffer as a GL texture and a Dispmanx resource, we had to prepare for both. In the worst case, that means one GL texture, and two (double-buffered case) Dispmanx resources, all the size of a whole surface, for all surfaces. This was eating memory fast. To make things worse (and less slow), the wl_shm buffer was kept around, since it was copied to either a texture or a resource as needed. This caused all clients to need two buffers. In a Dispmanx-only renderer, we can drop the GL texture, and we can release wl_shm buffer immediately after the first copy, so clients become effectively single-buffered. So from the worst case of 5 buffers per surface, we go down to 3 or just 2 (single-buffered Dispmanx element, one wl_shm buffer in the client) buffers per surface. As this will replace the GL renderer on rpi, we cannot fall back to the GLESv2 compositing anymore. We lose arbitrary surface rotation, but we lose also the GL fallback, which caused glitches. This patch depends on new RaspberryPi firmware. Older firmware may not render ARGB surfaces correctly, solid color surfaces maybe cause a performance hit, and the output may completely fail in case the firmware does not fall back internal off-line compositing properly as needed. This new rpi-renderer support surface translation and scaling, but not rotation or transpose (not even in 90-deg steps). In theory, 90-deg step surface rotation is possible to support. Output transformations are supported, but flipped variants do not seem to work right. As a detail, menus and other surfaces that are simply translated with respect to another surface caused falling back to the GL renderer. The rpi-renderer handles them directly. This patch only adds the new renderer, but does not hook it up into use. Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
12 years ago
/*
* 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 <stdlib.h>
#include <assert.h>
#include <string.h>
#include "config.h"
#ifdef HAVE_BCM_HOST
# include <bcm_host.h>
#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);
}