Patchwork Add QEMU DirectFB display driver

login
register
mail settings
Submitter Julian Pidancet
Date May 14, 2010, 4:20 p.m.
Message ID <1273854011-14909-1-git-send-email-julian.pidancet@citrix.com>
Download mbox | patch
Permalink /patch/52658/
State New
Headers show

Comments

Julian Pidancet - May 14, 2010, 4:20 p.m.
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.
As an example, the SDL library gives no garantee that the surface
pointer returned by SDL_SetVideoMode(), if called with the
SDL_HWSURFACE, will be located in video memory [1], especially if the
SDL main surface is not fullscreen. For this reason, you can never
assume that SDL will not perform extra copy operations on your behalf
without notifying you.

[1] http://www.libsdl.org/cgi/docwiki.cgi/SDL_SetVideoMode

Signed-off-by: Julian Pidancet <julian.pidancet@citrix.com>
---
 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

Patch

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 <console.h>
+#include <keymaps.h>
+
+#include <directfb.h>
+
+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, &region, 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);