From patchwork Thu May 13 15:53:58 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Julian Pidancet X-Patchwork-Id: 52657 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 0B34EB7E87 for ; Sat, 15 May 2010 05:12:03 +1000 (EST) Received: from localhost ([127.0.0.1]:33720 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1OD0Ih-0006zC-Us for incoming@patchwork.ozlabs.org; Fri, 14 May 2010 15:12:00 -0400 Received: from [140.186.70.92] (port=44448 helo=eggs.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1OCak2-0007kW-0s for qemu-devel@nongnu.org; Thu, 13 May 2010 11:54:31 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.69) (envelope-from ) id 1OCajv-0004SC-Cp for qemu-devel@nongnu.org; Thu, 13 May 2010 11:54:29 -0400 Received: from smtp02.citrix.com ([66.165.176.63]:18451) by eggs.gnu.org with esmtp (Exim 4.69) (envelope-from ) id 1OCajv-0004Rt-0C for qemu-devel@nongnu.org; Thu, 13 May 2010 11:54:23 -0400 X-IronPort-AV: E=Sophos;i="4.53,223,1272859200"; d="scan'208";a="96802097" Received: from ftlpexchmx01.citrite.net ([10.9.154.126]) by FTLPIPO02.CITRIX.COM with ESMTP; 13 May 2010 11:54:21 -0400 Received: from smtp01.ad.xensource.com ([10.219.128.104]) by FTLPEXCHMX01.citrite.net with Microsoft SMTPSVC(6.0.3790.4675); Thu, 13 May 2010 11:54:20 -0400 Received: from localhost.localdomain ([10.80.248.100]) by smtp01.ad.xensource.com (8.13.1/8.13.1) with ESMTP id o4DFsJD1028672; Thu, 13 May 2010 08:54:20 -0700 From: Julian Pidancet To: qemu-devel@nongnu.org Date: Thu, 13 May 2010 16:53:58 +0100 Message-Id: <1273766038-16808-1-git-send-email-julian.pidancet@citrix.com> X-Mailer: git-send-email 1.7.0 X-OriginalArrivalTime: 13 May 2010 15:54:20.0805 (UTC) FILETIME=[8D0E7350:01CAF2B4] X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Mailman-Approved-At: Fri, 14 May 2010 15:10:19 -0400 Cc: Julian Pidancet Subject: [Qemu-devel] [PATCH] Add QEMU DirectFB display driver X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org This patch implements a DirectFB driver for QEMU. It allows Qemu to draw a VM graphic output directly in the framebuffer of the host, without having to rely on X11. DirectFB also provides with a generic interface take advantage of graphic hardware acceleration for a bunch of different supported cards. In this driver, the DirectFB library gives Qemu a pointer to mapped video memory, which allows Qemu to update the display without extra copy. In the case where the guest framebuffer is not compatible with the host framebuffer, DirectFB surface blitting functions are used and can be accellerated wherever it is possible with the hardware. DirectFB is a thin library heavily used in embedded or minimal systems which don't require X11 overhead. One use case would be to build a Xen-based client-class hypervisor, with a minimal dom0 running Qemu as device-model. The dom0 could render the domU graphical outputs on the physical screen using this patch without having X11 installed. The other solution would be to use the DirectFB driver for SDL which would allow to do slightly the same as this patch. But that would mean having to deal with an additional layer in the graphical stack, which is not exactly what one wants from a performance or a complexity point of view. Signed-off-by: Julian Pidancet --- Makefile | 4 + Makefile.objs | 1 + configure | 21 +++ console.h | 3 + directfb.c | 394 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ qemu-options.hx | 10 ++ sysemu.h | 1 + vl.c | 12 ++ 8 files changed, 446 insertions(+), 0 deletions(-) create mode 100644 directfb.c diff --git a/Makefile b/Makefile index eb9e02b..6932c81 100644 --- a/Makefile +++ b/Makefile @@ -106,6 +106,10 @@ sdl.o: sdl.c keymaps.h sdl_keysym.h sdl_zoom.h sdl.o audio/sdlaudio.o sdl_zoom.o baum.o: QEMU_CFLAGS += $(SDL_CFLAGS) +directfb.o: directfb.c + +directfb.o: QEMU_CFLAGS += $(DIRECTFB_CFLAGS) + acl.o: acl.h acl.c vnc.h: vnc-tls.h vnc-auth-vencrypt.h vnc-auth-sasl.h keymaps.h diff --git a/Makefile.objs b/Makefile.objs index ecdd53e..0904b07 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -102,6 +102,7 @@ common-obj-y += $(addprefix audio/, $(audio-obj-y)) common-obj-y += keymaps.o common-obj-$(CONFIG_SDL) += sdl.o sdl_zoom.o x_keymap.o common-obj-$(CONFIG_CURSES) += curses.o +common-obj-$(CONFIG_DIRECTFB) += directfb.o common-obj-y += vnc.o acl.o d3des.o common-obj-y += vnc-encoding-zlib.o vnc-encoding-hextile.o common-obj-y += iov.o diff --git a/configure b/configure index 36d028f..eb73415 100755 --- a/configure +++ b/configure @@ -258,6 +258,7 @@ kvm="" kvm_para="" nptl="" sdl="" +directfb="no" sparse="no" uuid="" vde="" @@ -502,6 +503,10 @@ for opt do ;; --sysconfdir=*) sysconfdir="$optarg" ;; + --disable-directfb) directfb="no" + ;; + --enable-directfb) directfb="yes" + ;; --disable-sdl) sdl="no" ;; --enable-sdl) sdl="yes" @@ -763,6 +768,8 @@ echo " --disable-strip disable stripping binaries" echo " --disable-werror disable compilation abort on warning" echo " --disable-sdl disable SDL" echo " --enable-sdl enable SDL" +echo " --disable-directfb disable DirectFB" +echo " --enable-directfb enable DirectFB" echo " --enable-cocoa enable COCOA (Mac OS X only)" echo " --audio-drv-list=LIST set audio drivers list:" echo " Available drivers: $audio_possible_drivers" @@ -1062,6 +1069,15 @@ if test "$sparse" != "no" ; then fi ########################################## +# DirectFB probe + +if test "$directfb" = "yes" ; then + directfb_libs=`directfb-config --libs` + directfb_cflags=`directfb-config --cflags` + libs_softmmu="$directfb_libs $libs_softmmu" +fi + +########################################## # SDL probe if $pkgconfig sdl --modversion >/dev/null 2>&1; then @@ -1999,6 +2015,7 @@ if test "$darwin" = "yes" ; then echo "Cocoa support $cocoa" fi echo "SDL support $sdl" +echo "DirectFB support $directfb" echo "curses support $curses" echo "curl support $curl" echo "check support $check_utests" @@ -2169,6 +2186,10 @@ fi if test "$cocoa" = "yes" ; then echo "CONFIG_COCOA=y" >> $config_host_mak fi +if test "$directfb" = "yes" ; then + echo "CONFIG_DIRECTFB=y" >> $config_host_mak + echo "DIRECTFB_CFLAGS=$directfb_cflags" >> $config_host_mak +fi if test "$curses" = "yes" ; then echo "CONFIG_CURSES=y" >> $config_host_mak fi diff --git a/console.h b/console.h index 6def115..d1dd211 100644 --- a/console.h +++ b/console.h @@ -335,6 +335,9 @@ void qemu_console_resize(DisplayState *ds, int width, int height); void qemu_console_copy(DisplayState *ds, int src_x, int src_y, int dst_x, int dst_y, int w, int h); +/* directfb.c */ +void directfb_display_init(DisplayState *ds); + /* sdl.c */ void sdl_display_init(DisplayState *ds, int full_screen, int no_frame); diff --git a/directfb.c b/directfb.c new file mode 100644 index 0000000..6dea99a --- /dev/null +++ b/directfb.c @@ -0,0 +1,394 @@ +/* + * QEMU DirectFB display driver + * + * Copyright (c) 2010 Citrix Systems, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include + +static IDirectFB *dfb = NULL; +static IDirectFBSurface *primary = NULL; +static IDirectFBEventBuffer *events = NULL; +static IDirectFBSurface *guest = NULL; + +static void *screen_data = NULL; +static int screen_pitch = 0; +static int screen_bpp = 0; +static int screen_width = 0; +static int screen_height = 0; +static int scaling = 0; + +#define DIRECTFB_IS_VIDEO_PTR(p) \ + (p >= (uint8_t *) screen_data && \ + p < (uint8_t *) screen_data + screen_height * screen_pitch) + +static DFBSurfacePixelFormat directfb_bpp_to_pixelformat(int bpp) +{ + switch (bpp) { + case 16: + return DSPF_RGB16; + case 24: + return DSPF_RGB24; + case 32: + return DSPF_RGB32; + default: + return DSPF_UNKNOWN; + } +} + +static void directfb_clearscreen(void) +{ + if (screen_data != NULL) { + /* Surface is locked */ + memset(screen_data, 0x0, + screen_pitch * screen_height); + } else { + primary->SetColor(primary, 0x0, 0x0, 0x0, 0x0); + primary->FillRectangle(primary, 0, 0, screen_width, screen_height); + } +} + +static void directfb_update(struct DisplayState *s, int x, int y, int w, int h) +{ + DFBRegion region = {x, y, x + w, y + h}; + + if (guest) { + if (scaling) { + primary->StretchBlit(primary, guest, NULL, NULL); + } else { + int xoff = (screen_width - ds_get_width(s)) / 2; + int yoff = (screen_height - ds_get_height(s)) / 2; + + primary->Blit(primary, guest, NULL, xoff, yoff); + + region.x1 += xoff; + region.y1 += yoff; + region.x2 += xoff; + region.x2 += yoff; + } + } + + primary->Flip(primary, ®ion, DSFLIP_NONE); +} + +static void directfb_setdata(DisplayState *s) +{ + DFBSurfaceDescription dsc; + + if (guest) { + guest->Release(guest); + guest = NULL; + } + + dsc.flags = DSDESC_WIDTH | DSDESC_HEIGHT | + DSDESC_PIXELFORMAT | DSDESC_PREALLOCATED; + dsc.width = ds_get_width(s); + dsc.height = ds_get_height(s); + dsc.pixelformat = directfb_bpp_to_pixelformat(ds_get_bits_per_pixel(s)); + dsc.preallocated[0].data = ds_get_data(s); + dsc.preallocated[0].pitch = ds_get_linesize(s); + + dfb->CreateSurface(dfb, &dsc, &guest); +} + +static void directfb_resize(struct DisplayState *s) +{ + directfb_clearscreen(); + + if (scaling || ds_get_bits_per_pixel(s) != screen_bpp || + ds_get_linesize(s) != screen_pitch || + !DIRECTFB_IS_VIDEO_PTR(ds_get_data(s))) { + + directfb_setdata(s); + } else { + if (guest) { + guest->Release(guest); + guest = NULL; + } + } +} + +static int directfb_buttons_state(DFBInputEvent *ev) +{ + int buttons = 0; + + if (ev->buttons & DIBM_LEFT) { + buttons |= MOUSE_EVENT_LBUTTON; + } + if (ev->buttons & DIBM_RIGHT) { + buttons |= MOUSE_EVENT_RBUTTON; + } + if (ev->buttons & DIBM_MIDDLE) { + buttons |= MOUSE_EVENT_MBUTTON; + } + + return buttons; +} + +static void directfb_put_keycode(char keycode, int up) +{ + int scancode = keycode; + + /* Pause/Break */ + if (keycode == 119) { + scancode = 0x45; + kbd_put_keycode(0xe1); + kbd_put_keycode(0x1d | up ? 0x80 : 0x0); + } else { + /* grey key */ + if (keycode >= 0x60 && keycode < 0x70) { + const char esc[16] = {0x1c, 0x1d, 0x35, 0x37, + 0x38, 0x46, 0x47, 0x48, + 0x49, 0x4b, 0x4d, 0x4f, + 0x50, 0x51, 0x52, 0x53}; + scancode = esc[keycode - 0x60]; + kbd_put_keycode(0xe0); + + /* PrintScreen */ + if (keycode == 99) { + scancode = 0x37; + kbd_put_keycode(0x2a | up ? 0x80 : 0x0); + kbd_put_keycode(0xe0); + } + } + } + + kbd_put_keycode(scancode | (up ? 0x80 : 0x0)); +} + +static void directfb_toggle_fullscreen(struct DisplayState *ds) +{ + scaling = !scaling; + + vga_hw_invalidate(); + vga_hw_update(); +} + +static void directfb_refresh(struct DisplayState *s) +{ + DFBInputEvent ev; + + vga_hw_update(); + + while (events->GetEvent(events, DFB_EVENT(&ev)) == DFB_OK) { + switch (ev.type) { + case DIET_KEYRELEASE: + directfb_put_keycode(ev.key_code, 1); + break; + case DIET_KEYPRESS: + /* Toggle centered/fullscreen */ + if ((ev.modifiers & DIMM_CONTROL) && + (ev.modifiers & DIMM_ALT) && + (ev.key_id == DIKI_ENTER)) { + directfb_toggle_fullscreen(s); + break; + } + directfb_put_keycode(ev.key_code, 0); + break; + case DIET_BUTTONPRESS: + case DIET_BUTTONRELEASE: + case DIET_AXISMOTION: + { + int buttons = directfb_buttons_state(&ev); + int dx = 0; + int dy = 0; + int dz = 0; + + if (ev.type == DIET_AXISMOTION) { + if (ev.axis == DIAI_X) { + dx = ev.axisrel; + } + if (ev.axis == DIAI_Y) { + dy = ev.axisrel; + } + if (ev.axis == DIAI_Z) { + dz = ev.axisrel; + } + } + + kbd_mouse_event(dx, dy, dz, buttons); + break; + } + case DIET_UNKNOWN: + default: + break; + + } + } +} + +static DisplaySurface* directfb_create_displaysurface(int width, int height) +{ + DisplaySurface *surface = (DisplaySurface*) qemu_mallocz(sizeof(DisplaySurface)); + DFBSurfacePixelFormat spf; + surface->width = width; + surface->height = height; + + primary->GetPixelFormat(primary, &spf); + + if (scaling) { + int bytes_per_pixel = DFB_BYTES_PER_PIXEL(spf); + + if (bytes_per_pixel != 2 && bytes_per_pixel != 4) { + bytes_per_pixel = 4; + } + + surface->pf = qemu_default_pixelformat(8 * bytes_per_pixel); + surface->linesize = width * bytes_per_pixel; + + surface->flags = QEMU_ALLOCATED_FLAG; + surface->data = qemu_mallocz(surface->linesize * surface->height); + } else { + primary->Lock(primary, DSLF_READ | DSLF_WRITE, &screen_data, &screen_pitch); + surface->pf = qemu_default_pixelformat(screen_bpp); + surface->flags = QEMU_REALPIXELS_FLAG; + surface->linesize = screen_pitch; + surface->data = screen_data + + ((screen_height - height) / 2) * screen_pitch + + ((screen_width - width) / 2) * (screen_bpp / 8); + } + + return surface; +} + +static void directfb_free_displaysurface(DisplaySurface *surface) +{ + if (surface == NULL) + return; + + if (surface->flags & QEMU_ALLOCATED_FLAG) { + qemu_free(surface->data); + } else if (surface->flags & QEMU_REALPIXELS_FLAG) { + primary->Unlock(primary); + screen_data = NULL; + screen_pitch = 0; + } + + surface->data = NULL; + + qemu_free(surface); +} + +static DisplaySurface* directfb_resize_displaysurface(DisplaySurface *surface, + int width, + int height) +{ + directfb_free_displaysurface(surface); + return directfb_create_displaysurface(width, height); +} + +static DFBEnumerationResult directfb_attach_inputdevice(DFBInputDeviceID device_id, + DFBInputDeviceDescription desc, + void *data) +{ + if (!strcmp(desc.vendor, "Linux")) { + return DFENUM_OK; + } + + if (desc.type == DIDID_KEYBOARD || desc.type | DIDTF_MOUSE) { + IDirectFBInputDevice *device; + + dfb->GetInputDevice(dfb, device_id, &device); + + if (events == NULL) { + device->CreateEventBuffer(device, &events); + } else { + device->AttachEventBuffer(device, events); + } + } + + return DFENUM_OK; +} + +void directfb_display_init(DisplayState *ds) +{ + DisplayChangeListener *dcl; + DisplayAllocator *da; + DFBResult status; + DFBSurfaceDescription dsc; + DFBSurfaceCapabilities caps; + DFBSurfacePixelFormat spf; + + /* + * Prevent DirectFB to read qemu command line argument in procfs and + * parse it. + */ + char prog_name[] = "qemu"; + char *prog_argv[] = {prog_name}; + char **dfb_argv = prog_argv; + int dfb_argc = 1; + + status = DirectFBInit(&dfb_argc, &dfb_argv); + if (status != DFB_OK) { + fprintf(stderr, "Could not initialize DirectFB(%d) - exiting\n", status); + exit(1); + } + + DirectFBCreate(&dfb); + dfb->SetCooperativeLevel(dfb, DFSCL_FULLSCREEN); + dsc.flags = DSDESC_CAPS; + dsc.caps = DSCAPS_PRIMARY | DSCAPS_VIDEOONLY | DSCAPS_SHARED; + status = dfb->CreateSurface(dfb, &dsc, &primary); + + if (status != DFB_OK) { + fprintf(stderr, "Could not create DirectFB surface(%d) - exiting\n", status); + exit(1); + } + + /* Double check surface capabilities */ + primary->GetCapabilities(primary, &caps); + if ((caps & dsc.caps) != dsc.caps || + caps & DSCAPS_FLIPPING || caps & DSCAPS_INTERLACED || + caps & DSCAPS_SYSTEMONLY) { + fprintf(stderr, "Wrong DirectFB surface capabilities - exiting\n"); + exit(1); + } + + primary->GetSize(primary, &screen_width, &screen_height); + primary->GetPixelFormat(primary, &spf); + screen_bpp = DFB_BITS_PER_PIXEL(spf); + + dfb->EnumInputDevices(dfb, directfb_attach_inputdevice, NULL); + + fprintf(stderr, "Initialized QEMU DirectFB driver. (%dx%d)\n", + screen_width, screen_height); + + dcl = qemu_mallocz(sizeof(DisplayChangeListener)); + dcl->dpy_update = directfb_update; + dcl->dpy_resize = directfb_resize; + dcl->dpy_refresh = directfb_refresh; + dcl->dpy_setdata = directfb_setdata; + register_displaychangelistener(ds, dcl); + + da = qemu_mallocz(sizeof(DisplayAllocator)); + da->create_displaysurface = directfb_create_displaysurface; + da->resize_displaysurface = directfb_resize_displaysurface; + da->free_displaysurface = directfb_free_displaysurface; + + directfb_clearscreen(); + + if (register_displayallocator(ds, da) == da) { + dpy_resize(ds); + } +} diff --git a/qemu-options.hx b/qemu-options.hx index 12f6b51..a4bdfbe 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -608,6 +608,16 @@ QEMU can display the VGA output when in text mode using a curses/ncurses interface. Nothing is displayed in graphical mode. ETEXI +#ifdef CONFIG_DIRECTFB +DEF("directfb", 0, QEMU_OPTION_directfb, + "-directfb enable DirectFB\n") +#endif +STEXI +@item -directfb +@findex -directfb +Enable DirectFB. +ETEXI + #ifdef CONFIG_SDL DEF("no-frame", 0, QEMU_OPTION_no_frame, "-no-frame open SDL window without a frame and window decorations\n", diff --git a/sysemu.h b/sysemu.h index fa921df..a2cd5b0 100644 --- a/sysemu.h +++ b/sysemu.h @@ -98,6 +98,7 @@ typedef enum DisplayType DT_CURSES, DT_SDL, DT_VNC, + DT_DIRECTFB, DT_NOGRAPHIC, } DisplayType; diff --git a/vl.c b/vl.c index 85bcc84..e6235fa 100644 --- a/vl.c +++ b/vl.c @@ -3199,6 +3199,11 @@ int main(int argc, char **argv, char **envp) case QEMU_OPTION_full_screen: full_screen = 1; break; +#ifdef CONFIG_DIRECTFB + case QEMU_OPTION_directfb: + display_type = DT_DIRECTFB; + break; +#endif #ifdef CONFIG_SDL case QEMU_OPTION_no_frame: no_frame = 1; @@ -3765,6 +3770,8 @@ int main(int argc, char **argv, char **envp) if (display_type == DT_DEFAULT) { #if defined(CONFIG_SDL) || defined(CONFIG_COCOA) display_type = DT_SDL; +#elif defined(CONFIG_DIRECTFB) + display_type = DT_DIRECTFB; #else display_type = DT_VNC; vnc_display = "localhost:0,to=99"; @@ -3781,6 +3788,11 @@ int main(int argc, char **argv, char **envp) curses_display_init(ds, full_screen); break; #endif +#if defined(CONFIG_DIRECTFB) + case DT_DIRECTFB: + directfb_display_init(ds); + break; +#endif #if defined(CONFIG_SDL) case DT_SDL: sdl_display_init(ds, full_screen, no_frame);