[PATCH v3] fbdev: Add an fbdev compositor backend using pixman and evdev
Pekka Paalanen
ppaalanen at gmail.com
Wed Jan 23 00:33:28 PST 2013
Hi Philip,
I still didn't test this, but it looks good. I have some minor comments
inline below.
On Tue, 22 Jan 2013 20:59:44 +0000
Philip Withnall <philip at tecnocode.co.uk> wrote:
> Here's the third version of the fbdev backend. This includes all the
> fixes from the second version, plus a fix to use weston_launcher_open()
> instead of open() for evdev devices, as pointed out by MoD.
>
> This patch is a squashed and tidied up version of various commits on my
> branch:
> https://gitorious.org/~pwithnall/weston/pwithnalls-weston/commits/compositor-fbdev.
> As far as I'm concerned, it's now ready to be merged, pending a final
> round of review (since there weren't any replies to v2 of the patch).
>
The above probably is not meant to end up in the git history, is it? If
so, you should put it after the "---" line, where the diffstat is too.
That way it gets discarded automatically on committing.
>
> Add a frame buffer backend using pixman to render to fbdev.
> This has been tested against nouveaufb but nothing else. Much of the code
> came straight from the rpi backend (and copyright has been attributed
> accordingly).
>
> The behaviour of this backend on less modern frame buffers has yet to be
> tested.
>
> The refresh rate is calculated from the frame buffer's metadata. Every frame
> is finished in synchrony with the refresh rate.
>
> Frame buffer devices are currently specified on the command line (or using
> the default of /dev/fb0); udev could be used in future to enumerate them.
>
> pixman is used for compositing, and a suitable pixman format is built from
> the frame buffer's metadata. This doesn't support the full range of
> frame buffer formats, but does support varying BPPs of RGBA and ARGB. That
> should be enough for now.
>
> The following are not currently supported:
> • FOURCC
> • Non-packed formats (interleaved, planes, etc.)
> • Non-true-colour formats (monochrome, greyscale, etc.)
> • Big-endian formats (with component MSBs on the right)
> • Non-RGBA and non-ARGB formats
>
> Signed-off-by: Philip Withnall <philip at tecnocode.co.uk>
> ---
> configure.ac | 10 +
> src/Makefile.am | 27 +-
> src/compositor-fbdev.c | 960 +++++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 996 insertions(+), 1 deletion(-)
> create mode 100644 src/compositor-fbdev.c
>
> diff --git a/configure.ac b/configure.ac
> index 64505dd..57a444b 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -154,6 +154,16 @@ fi
> AM_CONDITIONAL(INSTALL_RPI_COMPOSITOR, test "x$have_bcm_host" = "xyes")
>
>
> +AC_ARG_ENABLE([fbdev-compositor], [ --enable-fbdev-compositor],,
> + enable_fbdev_compositor=yes)
> +AM_CONDITIONAL([ENABLE_FBDEV_COMPOSITOR],
> + [test x$enable_fbdev_compositor = xyes])
> +AS_IF([test x$enable_fbdev_compositor = xyes], [
> + AC_DEFINE([BUILD_FBDEV_COMPOSITOR], [1], [Build the fbdev compositor])
> + PKG_CHECK_MODULES([FBDEV_COMPOSITOR], [libudev >= 136 mtdev >= 1.1.0])
> +])
> +
> +
> AC_ARG_WITH(cairo-glesv2,
> AS_HELP_STRING([--with-cairo-glesv2],
> [Use GLESv2 cairo instead of full GL]))
> diff --git a/src/Makefile.am b/src/Makefile.am
> index cbfa911..ccbb4bd 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -84,7 +84,8 @@ module_LTLIBRARIES = \
> $(x11_backend) \
> $(drm_backend) \
> $(wayland_backend) \
> - $(headless_backend)
> + $(headless_backend) \
> + $(fbdev_backend)
>
> # Do not install, since the binary produced via autotools is unusable.
> # The real backend is built by the Android build system.
> @@ -195,6 +196,30 @@ headless_backend_la_CFLAGS = \
> headless_backend_la_SOURCES = compositor-headless.c
> endif
>
> +if ENABLE_FBDEV_COMPOSITOR
> +fbdev_backend = fbdev-backend.la
> +fbdev_backend_la_LDFLAGS = -module -avoid-version
> +fbdev_backend_la_LIBADD = \
> + $(COMPOSITOR_LIBS) \
> + $(FBDEV_COMPOSITOR_LIBS) \
> + ../shared/libshared.la \
> + $(NULL)
> +fbdev_backend_la_CFLAGS = \
> + $(COMPOSITOR_CFLAGS) \
> + $(FBDEV_COMPOSITOR_CFLAGS) \
> + $(PIXMAN_CFLAGS) \
> + $(GCC_CFLAGS) \
> + $(NULL)
> +fbdev_backend_la_SOURCES = \
> + compositor-fbdev.c \
> + tty.c \
> + evdev.c \
> + evdev.h \
> + evdev-touchpad.c \
> + launcher-util.c \
> + $(NULL)
> +endif
This will be first time using $(NULL), and I believe it relies on
nothing ever assigning a value to variable NULL (grep finds no NULL at
all in currently generated Makefiles). *shrug*
> +
> if ENABLE_DESKTOP_SHELL
> desktop_shell = desktop-shell.la
> desktop_shell_la_LDFLAGS = -module -avoid-version
> diff --git a/src/compositor-fbdev.c b/src/compositor-fbdev.c
> new file mode 100644
> index 0000000..9674017
> --- /dev/null
> +++ b/src/compositor-fbdev.c
> @@ -0,0 +1,960 @@
> +/*
> + * Copyright © 2008-2011 Kristian Høgsberg
> + * Copyright © 2011 Intel Corporation
> + * Copyright © 2012 Raspberry Pi Foundation
> + * Copyright © 2013 Philip Withnall
> + *
> + * 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.
> + */
> +
> +#define _GNU_SOURCE
> +
> +#include <errno.h>
> +#include <stdlib.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <math.h>
> +#include <sys/mman.h>
> +#include <sys/types.h>
> +#include <fcntl.h>
> +#include <unistd.h>
> +#include <linux/fb.h>
> +
> +#include <libudev.h>
> +
> +#include "config.h"
> +
> +#include "compositor.h"
> +#include "launcher-util.h"
> +#include "pixman-renderer.h"
> +#include "evdev.h"
> +
> +struct fbdev_compositor {
> + struct weston_compositor base;
> + uint32_t prev_state;
> +
> + struct udev *udev;
> + struct tty *tty;
> +};
> +
> +struct fbdev_output {
> + struct fbdev_compositor *compositor;
> + struct weston_output base;
> +
> + struct weston_mode mode;
> + struct wl_event_source *finish_frame_timer;
> +
> + /* Frame buffer details. */
> + struct fb_fix_screeninfo fixed_info;
> + struct fb_var_screeninfo variable_info;
> + void *fb;
> + size_t fb_len;
> +
> + /* pixman details. */
> + pixman_image_t *hw_surface;
> + pixman_image_t *shadow_surface;
> + void *shadow_buf;
> + uint8_t depth;
> +};
> +
> +struct fbdev_seat {
> + struct weston_seat base;
> + struct wl_list devices_list;
> +
> + struct udev_monitor *udev_monitor;
> + struct wl_event_source *udev_monitor_source;
> + char *seat_id;
> +};
> +
> +struct fbdev_parameters {
> + int tty;
> + char *device;
> +};
> +
> +static inline struct fbdev_output *
> +to_fbdev_output(struct weston_output *base)
> +{
> + return container_of(base, struct fbdev_output, base);
> +}
> +
> +static inline struct fbdev_seat *
> +to_fbdev_seat(struct weston_seat *base)
> +{
> + return container_of(base, struct fbdev_seat, base);
> +}
> +
> +static inline struct fbdev_compositor *
> +to_fbdev_compositor(struct weston_compositor *base)
> +{
> + return container_of(base, struct fbdev_compositor, base);
> +}
> +
> +static void
> +fbdev_output_repaint(struct weston_output *base, pixman_region32_t *damage)
> +{
> + struct fbdev_output *output = to_fbdev_output(base);
> + struct weston_compositor *ec = output->base.compositor;
> + pixman_box32_t *rects;
> + int nrects, i, src_x, src_y, x1, y1, x2, y2, width, height;
> +
> + /* Repaint the damaged region onto the back buffer. */
> + pixman_renderer_output_set_buffer(base, output->shadow_surface);
> + ec->renderer->repaint_output(base, damage);
> +
> + /* Transform and composite onto the frame buffer. */
> + width = pixman_image_get_width(output->shadow_surface);
> + height = pixman_image_get_height(output->shadow_surface);
> + rects = pixman_region32_rectangles(damage, &nrects);
> +
> + for (i = 0; i < nrects; i++) {
> + switch (base->transform) {
> + default:
> + case WL_OUTPUT_TRANSFORM_NORMAL:
> + x1 = rects[i].x1;
> + x2 = rects[i].x2;
> + y1 = rects[i].y1;
> + y2 = rects[i].y2;
> + break;
> + case WL_OUTPUT_TRANSFORM_180:
> + x1 = width - rects[i].x2;
> + x2 = width - rects[i].x1;
> + y1 = height - rects[i].y2;
> + y2 = height - rects[i].y1;
> + break;
> + case WL_OUTPUT_TRANSFORM_90:
> + x1 = height - rects[i].y2;
> + x2 = height - rects[i].y1;
> + y1 = rects[i].x1;
> + y2 = rects[i].x2;
> + break;
> + case WL_OUTPUT_TRANSFORM_270:
> + x1 = rects[i].y1;
> + x2 = rects[i].y2;
> + y1 = width - rects[i].x2;
> + y2 = width - rects[i].x1;
> + break;
> + }
> + src_x = x1;
> + src_y = y1;
> +
> + pixman_image_composite32(PIXMAN_OP_SRC,
> + output->shadow_surface, /* src */
> + NULL /* mask */,
> + output->hw_surface, /* dest */
> + src_x, src_y, /* src_x, src_y */
> + 0, 0, /* mask_x, mask_y */
> + x1, y1, /* dest_x, dest_y */
> + x2 - x1, /* width */
> + y2 - y1 /* height */);
> + }
> +
> + /* Update the damage region. */
> + pixman_region32_subtract(&ec->primary_plane.damage,
> + &ec->primary_plane.damage, damage);
> +
> + /* Schedule the end of the frame. We do not sync this to the frame
> + * buffer clock because users who want that should be using the DRM
> + * compositor. FBIO_WAITFORVSYNC blocks and FB_ACTIVATE_VBL requires
> + * panning, which is broken in most kernel drivers.
> + *
> + * Finish the frame synchronised to the specified refresh rate. The
> + * refresh rate is given in mHz and the interval in ms. */
> + wl_event_source_timer_update(output->finish_frame_timer,
> + 1000000 / output->mode.refresh);
> +}
> +
> +static int
> +finish_frame_handler(void *data)
> +{
> + struct fbdev_output *output = data;
> + uint32_t msec;
> + struct timeval tv;
> +
> + gettimeofday(&tv, NULL);
> + msec = tv.tv_sec * 1000 + tv.tv_usec / 1000;
> + weston_output_finish_frame(&output->base, msec);
> +
> + return 1;
> +}
> +
> +static int
> +fbdev_frame_buffer_create(struct fbdev_output *output, const char *fb_dev)
> +{
> + int retval = -1;
> + int fd = -1;
> +
> + /* Open the frame buffer device. */
> + fd = open (fb_dev, O_RDWR | O_CLOEXEC);
Extra space.
> + if (fd < 0) {
> + weston_log("Failed to open frame buffer device ‘%s’: %s\n",
> + fb_dev, strerror(errno));
> + goto err;
> + }
> +
> + /* Grab the screen info. */
> + if (ioctl(fd, FBIOGET_FSCREENINFO, &output->fixed_info) < 0 ||
> + ioctl(fd, FBIOGET_VSCREENINFO, &output->variable_info) < 0) {
> + weston_log("Failed to get frame buffer info: %s\n",
> + strerror(errno));
> + goto err;
> + }
> +
> + /* Map the frame buffer. Write-only mode, since we don't want to read
> + * anything back (because it's slow). */
> + output->fb = mmap (NULL, output->fixed_info.smem_len,
Extra space.
> + PROT_WRITE, MAP_SHARED, fd, 0);
> + if (output->fb == MAP_FAILED) {
> + weston_log("Failed to mmap frame buffer: %s\n",
> + strerror(errno));
> + goto err;
> + }
> +
> + /* Success! */
> + output->fb_len = output->fixed_info.smem_len;
> + retval = 0;
> +
> +err:
> + if (fd >= 0)
> + close (fd);
Extra space.
> +
> + return retval;
> +}
> +
> +static void
> +fbdev_frame_buffer_destroy(struct fbdev_output *output)
> +{
> + if (munmap(output->fb, output->fb_len) < 0)
> + weston_log("Failed to munmap frame buffer: %s\n",
> + strerror(errno));
> +
> + output->fb = NULL;
> + output->fb_len = 0;
> +}
> +
> +static pixman_format_code_t
> +calculate_pixman_format(struct fb_var_screeninfo *vinfo,
> + struct fb_fix_screeninfo *finfo)
> +{
> + /* Calculate the pixman format supported by the frame buffer from the
> + * buffer's metadata. Return 0 if no known pixman format is supported
> + * (since this has depth 0 it's guaranteed to not conflict with any
> + * actual pixman format).
> + *
> + * Documentation on the vinfo and finfo structures:
> + * http://www.mjmwired.net/kernel/Documentation/fb/api.txt
> + *
> + * TODO: Try a bit harder to support other formats, including setting
> + * the preferred format in the hardware. */
> + int type;
> +
> + weston_log("Calculating pixman format from:\n"
> + STAMP_SPACE " - type: %i (aux: %i)\n"
> + STAMP_SPACE " - visual: %i\n"
> + STAMP_SPACE " - bpp: %i (grayscale: %i)\n"
> + STAMP_SPACE " - red: offset: %i, length: %i, MSB: %i\n"
> + STAMP_SPACE " - green: offset: %i, length: %i, MSB: %i\n"
> + STAMP_SPACE " - blue: offset: %i, length: %i, MSB: %i\n"
> + STAMP_SPACE " - transp: offset: %i, length: %i, MSB: %i\n",
> + finfo->type, finfo->type_aux, finfo->visual,
> + vinfo->bits_per_pixel, vinfo->grayscale,
> + vinfo->red.offset, vinfo->red.length, vinfo->red.msb_right,
> + vinfo->green.offset, vinfo->green.length,
> + vinfo->green.msb_right,
> + vinfo->blue.offset, vinfo->blue.length,
> + vinfo->blue.msb_right,
> + vinfo->transp.offset, vinfo->transp.length,
> + vinfo->transp.msb_right);
> +
> + /* We only handle packed formats at the moment. */
> + if (finfo->type != FB_TYPE_PACKED_PIXELS)
> + return 0;
> +
> + /* We only handle true-colour frame buffers at the moment. */
> + if (finfo->visual != FB_VISUAL_TRUECOLOR || vinfo->grayscale != 0)
> + return 0;
> +
> + /* We only support formats with MSBs on the left. */
> + if (vinfo->red.msb_right != 0 || vinfo->green.msb_right != 0 ||
> + vinfo->blue.msb_right != 0)
> + return 0;
> +
> + /* Work out the format type from the offsets. We only support RGBA and
> + * ARGB at the moment. */
> + type = PIXMAN_TYPE_OTHER;
> +
> + if ((vinfo->transp.offset >= vinfo->red.offset ||
> + vinfo->transp.length == 0) &&
> + vinfo->red.offset >= vinfo->green.offset &&
> + vinfo->green.offset >= vinfo->blue.offset)
> + type = PIXMAN_TYPE_ARGB;
> + else if (vinfo->red.offset >= vinfo->green.offset &&
> + vinfo->green.offset >= vinfo->blue.offset &&
> + vinfo->blue.offset >= vinfo->transp.offset)
> + type = PIXMAN_TYPE_RGBA;
> +
> + if (type == PIXMAN_TYPE_OTHER)
> + return 0;
> +
> + /* Build the format. */
> + return PIXMAN_FORMAT(vinfo->bits_per_pixel, type,
> + vinfo->transp.length,
> + vinfo->red.length,
> + vinfo->green.length,
> + vinfo->blue.length);
> +}
> +
> +static int
> +calculate_refresh_rate(struct fb_var_screeninfo *vinfo)
> +{
> + uint64_t quot;
> +
> + /* Calculate monitor refresh rate. Default is 60 Hz. Units are mHz. */
> + quot = (vinfo->upper_margin + vinfo->lower_margin + vinfo->yres);
> + quot *= (vinfo->left_margin + vinfo->right_margin + vinfo->xres);
> + quot *= vinfo->pixclock;
> +
> + if (quot > 0) {
> + uint64_t refresh_rate;
> +
> + refresh_rate = 1000000000000000LLU / quot;
> + if (refresh_rate > 200000)
> + refresh_rate = 200000; /* cap at 200 Hz */
> +
> + return refresh_rate;
> + }
> +
> + return 60 * 1000; /* default to 60 Hz */
> +}
> +
> +static void fbdev_output_destroy(struct weston_output *base);
> +
> +static int
> +fbdev_output_create(struct fbdev_compositor *compositor,
> + struct fbdev_parameters *param)
> +{
> + struct fbdev_output *output;
> + pixman_transform_t transform;
> + int shadow_width, shadow_height;
> + int width, height;
> + pixman_format_code_t pixman_format;
> + struct wl_event_loop *loop;
> +
> + output = calloc(1, sizeof *output);
> + if (!output)
> + return -1;
> +
> + output->compositor = compositor;
> +
> + /* Create the frame buffer. */
> + if (fbdev_frame_buffer_create(output, param->device) < 0) {
> + weston_log("Creating frame buffer failed.\n");
> + goto out_free;
> + }
> +
> + pixman_format = calculate_pixman_format(&output->variable_info, &output->fixed_info);
> + if (pixman_format == 0) {
> + weston_log("Frame buffer uses an unsupported format.\n");
> + goto out_fb;
> + }
> +
> + output->base.repaint = fbdev_output_repaint;
> + output->base.destroy = fbdev_output_destroy;
> + output->base.assign_planes = NULL;
> + output->base.set_backlight = NULL;
> + output->base.set_dpms = NULL;
> + output->base.switch_mode = NULL;
> +
> + /* only one static mode in list */
> + output->mode.flags =
> + WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED;
> + output->mode.width = output->variable_info.xres;
> + output->mode.height = output->variable_info.yres;
> + output->mode.refresh = calculate_refresh_rate(&output->variable_info);
> + wl_list_init(&output->base.mode_list);
> + wl_list_insert(&output->base.mode_list, &output->mode.link);
> +
> + output->base.current = &output->mode;
> + output->base.origin = &output->mode;
> + output->base.subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN;
> + output->base.make = "fbdev";
> + output->base.model = output->fixed_info.id;
These are meant for the monitor make and model, and are transmitted to
clients. I think "fbdev" is not right, since there actually is a real
monitor, so make could be "unknown".
> +
> + weston_output_init(&output->base, &compositor->base,
> + 0, 0, output->variable_info.width,
> + output->variable_info.height,
> + WL_OUTPUT_TRANSFORM_NORMAL);
> +
> + /* Create a pixman image to wrap the memory mapped frame buffer. */
> + width = output->variable_info.xres;
> + height = output->variable_info.yres;
> +
> + output->hw_surface =
> + pixman_image_create_bits(pixman_format, width, height,
> + output->fb,
> + output->fixed_info.line_length);
> + if (output->hw_surface == NULL) {
> + weston_log("Failed to create surface for frame buffer.\n");
> + goto out_output;
> + }
> +
> + pixman_transform_init_identity(&transform);
> + switch (output->base.transform) {
> + default:
> + case WL_OUTPUT_TRANSFORM_NORMAL:
> + shadow_width = width;
> + shadow_height = height;
> + pixman_transform_rotate(&transform,
> + NULL, 0, 0);
> + pixman_transform_translate(&transform, NULL,
> + 0, 0);
> + break;
> + case WL_OUTPUT_TRANSFORM_180:
> + shadow_width = width;
> + shadow_height = height;
> + pixman_transform_rotate(&transform,
> + NULL, -pixman_fixed_1, 0);
> + pixman_transform_translate(NULL, &transform,
> + pixman_int_to_fixed(shadow_width),
> + pixman_int_to_fixed(shadow_height));
> + break;
> + case WL_OUTPUT_TRANSFORM_270:
> + shadow_width = height;
> + shadow_height = width;
> + pixman_transform_rotate(&transform,
> + NULL, 0, pixman_fixed_1);
> + pixman_transform_translate(&transform,
> + NULL,
> + pixman_int_to_fixed(shadow_width),
> + 0);
> + break;
> + case WL_OUTPUT_TRANSFORM_90:
> + shadow_width = height;
> + shadow_height = width;
> + pixman_transform_rotate(&transform,
> + NULL, 0, -pixman_fixed_1);
> + pixman_transform_translate(&transform,
> + NULL,
> + 0,
> + pixman_int_to_fixed(shadow_height));
> + break;
> + }
> +
> + output->shadow_buf = malloc(width * height * (output->variable_info.bits_per_pixel / 8));
> + output->shadow_surface =
> + pixman_image_create_bits(pixman_format, shadow_width,
> + shadow_height, output->shadow_buf,
> + shadow_width * (output->variable_info.bits_per_pixel / 8));
Way long lines, maybe a helper variable for bytes per pixel?
Hmm, so the shadow buffer is in the same format as the framebuffer,
right? Would it be better for the renderer to have the shadow buffer in
a default format, and convert only on blit? I think that way the
renderer could work with its most performant format for blending etc.
and do the fb-format conversion just once.
However, I see that anderco's patch moves the shadow buffer to the
renderer, so this issue should resolve naturally, once the two patch
sets get merged.
> + if (output->shadow_buf == NULL || output->shadow_surface == NULL) {
> + weston_log("Failed to create surface for frame buffer.\n");
> + goto out_hw_surface;
> + }
> +
> + /* No need in transform for normal output */
> + if (output->base.transform != WL_OUTPUT_TRANSFORM_NORMAL)
> + pixman_image_set_transform(output->shadow_surface, &transform);
> +
> + if (pixman_renderer_output_create(&output->base) < 0)
> + goto out_shadow_surface;
> +
> + loop = wl_display_get_event_loop(compositor->base.wl_display);
> + output->finish_frame_timer =
> + wl_event_loop_add_timer(loop, finish_frame_handler, output);
> +
> + wl_list_insert(compositor->base.output_list.prev, &output->base.link);
> +
> + weston_log("fbdev output %d×%d px\n",
That looks like an utf-8 cross-product character, is it? Is that
portable?
> + output->mode.width, output->mode.height);
> + weston_log_continue(STAMP_SPACE "guessing %d Hz and 96 dpi\n",
> + output->mode.refresh / 1000);
> +
> + return 0;
> +
> +out_shadow_surface:
> + pixman_image_unref(output->shadow_surface);
> +out_hw_surface:
> + free(output->shadow_buf);
> + pixman_image_unref(output->hw_surface);
> +out_output:
> + weston_output_destroy(&output->base);
> +out_fb:
> + fbdev_frame_buffer_destroy(output);
> +out_free:
> + free(output);
> +
> + return -1;
> +}
> +
> +static void
> +fbdev_output_destroy(struct weston_output *base)
> +{
> + struct fbdev_output *output = to_fbdev_output(base);
> +
> + weston_log("%s\n", __func__);
Forgotten debug print?
Thanks,
pq
More information about the wayland-devel
mailing list