Commit a19cbfb3 authored by Gerd Hoffmann's avatar Gerd Hoffmann
Browse files

spice: add qxl device



qxl is a paravirtual graphics card.  The qxl device is the bridge
between the guest and the spice server (aka libspice-server).  The
spice server will send the rendering commands to the spice client, which
will actually render them.

The spice server is also able to render locally, which is done in case
the guest wants read something from video memory.  Local rendering is
also used to support display over vnc and sdl.

qxl is activated using "-vga qxl".  qxl supports multihead, additional
cards can be added via '-device qxl".

[ v2: add copyright to files                     ]
[ v2: use qemu-common.h for standard includes    ]
[ v2: create separate qxl-vga device for primary ]

Signed-off-by: default avatarGerd Hoffmann <kraxel@redhat.com>
parent 7943a2fa
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -224,6 +224,7 @@ obj-i386-y += vmmouse.o vmport.o hpet.o applesmc.o
obj-i386-y += device-hotplug.o pci-hotplug.o smbios.o wdt_ib700.o
obj-i386-y += debugcon.o multiboot.o
obj-i386-y += pc_piix.o
obj-i386-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o

# shared objects
obj-ppc-y = ppc.o
+14 −0
Original line number Diff line number Diff line
@@ -528,6 +528,17 @@ extern const VMStateInfo vmstate_info_unused_buffer;
    .start        = (_start),                                        \
}

#define VMSTATE_VBUFFER_UINT32(_field, _state, _version, _test, _start, _field_size) { \
    .name         = (stringify(_field)),                             \
    .version_id   = (_version),                                      \
    .field_exists = (_test),                                         \
    .size_offset  = vmstate_offset_value(_state, _field_size, uint32_t),\
    .info         = &vmstate_info_buffer,                            \
    .flags        = VMS_VBUFFER|VMS_POINTER,                         \
    .offset       = offsetof(_state, _field),                        \
    .start        = (_start),                                        \
}

#define VMSTATE_BUFFER_UNSAFE_INFO(_field, _state, _version, _info, _size) { \
    .name       = (stringify(_field)),                               \
    .version_id = (_version),                                        \
@@ -745,6 +756,9 @@ extern const VMStateDescription vmstate_i2c_slave;
#define VMSTATE_PARTIAL_VBUFFER(_f, _s, _size)                        \
    VMSTATE_VBUFFER(_f, _s, 0, NULL, 0, _size)

#define VMSTATE_PARTIAL_VBUFFER_UINT32(_f, _s, _size)                        \
    VMSTATE_VBUFFER_UINT32(_f, _s, 0, NULL, 0, _size)

#define VMSTATE_SUB_VBUFFER(_f, _s, _start, _size)                    \
    VMSTATE_VBUFFER(_f, _s, 0, NULL, _start, _size)

+8 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@
#include "sysbus.h"
#include "sysemu.h"
#include "blockdev.h"
#include "ui/qemu-spice.h"

/* output Bochs bios info messages */
//#define DEBUG_BIOS
@@ -991,6 +992,13 @@ void pc_vga_init(PCIBus *pci_bus)
            pci_vmsvga_init(pci_bus);
        else
            fprintf(stderr, "%s: vmware_vga: no PCI bus\n", __FUNCTION__);
#ifdef CONFIG_SPICE
    } else if (qxl_enabled) {
        if (pci_bus)
            pci_create_simple(pci_bus, -1, "qxl-vga");
        else
            fprintf(stderr, "%s: qxl: no PCI bus\n", __FUNCTION__);
#endif
    } else if (std_vga_enabled) {
        if (pci_bus) {
            pci_vga_init(pci_bus);

hw/qxl-logger.c

0 → 100644
+248 −0
Original line number Diff line number Diff line
/*
 * qxl command logging -- for debug purposes
 *
 * Copyright (C) 2010 Red Hat, Inc.
 *
 * maintained by Gerd Hoffmann <kraxel@redhat.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 or
 * (at your option) version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include "qxl.h"

static const char *qxl_type[] = {
    [ QXL_CMD_NOP ]     = "nop",
    [ QXL_CMD_DRAW ]    = "draw",
    [ QXL_CMD_UPDATE ]  = "update",
    [ QXL_CMD_CURSOR ]  = "cursor",
    [ QXL_CMD_MESSAGE ] = "message",
    [ QXL_CMD_SURFACE ] = "surface",
};

static const char *qxl_draw_type[] = {
    [ QXL_DRAW_NOP         ] = "nop",
    [ QXL_DRAW_FILL        ] = "fill",
    [ QXL_DRAW_OPAQUE      ] = "opaque",
    [ QXL_DRAW_COPY        ] = "copy",
    [ QXL_COPY_BITS        ] = "copy-bits",
    [ QXL_DRAW_BLEND       ] = "blend",
    [ QXL_DRAW_BLACKNESS   ] = "blackness",
    [ QXL_DRAW_WHITENESS   ] = "whitemess",
    [ QXL_DRAW_INVERS      ] = "invers",
    [ QXL_DRAW_ROP3        ] = "rop3",
    [ QXL_DRAW_STROKE      ] = "stroke",
    [ QXL_DRAW_TEXT        ] = "text",
    [ QXL_DRAW_TRANSPARENT ] = "transparent",
    [ QXL_DRAW_ALPHA_BLEND ] = "alpha-blend",
};

static const char *qxl_draw_effect[] = {
    [ QXL_EFFECT_BLEND            ] = "blend",
    [ QXL_EFFECT_OPAQUE           ] = "opaque",
    [ QXL_EFFECT_REVERT_ON_DUP    ] = "revert-on-dup",
    [ QXL_EFFECT_BLACKNESS_ON_DUP ] = "blackness-on-dup",
    [ QXL_EFFECT_WHITENESS_ON_DUP ] = "whiteness-on-dup",
    [ QXL_EFFECT_NOP_ON_DUP       ] = "nop-on-dup",
    [ QXL_EFFECT_NOP              ] = "nop",
    [ QXL_EFFECT_OPAQUE_BRUSH     ] = "opaque-brush",
};

static const char *qxl_surface_cmd[] = {
   [ QXL_SURFACE_CMD_CREATE  ] = "create",
   [ QXL_SURFACE_CMD_DESTROY ] = "destroy",
};

static const char *spice_surface_fmt[] = {
   [ SPICE_SURFACE_FMT_INVALID  ] = "invalid",
   [ SPICE_SURFACE_FMT_1_A      ] = "alpha/1",
   [ SPICE_SURFACE_FMT_8_A      ] = "alpha/8",
   [ SPICE_SURFACE_FMT_16_555   ] = "555/16",
   [ SPICE_SURFACE_FMT_16_565   ] = "565/16",
   [ SPICE_SURFACE_FMT_32_xRGB  ] = "xRGB/32",
   [ SPICE_SURFACE_FMT_32_ARGB  ] = "ARGB/32",
};

static const char *qxl_cursor_cmd[] = {
   [ QXL_CURSOR_SET   ] = "set",
   [ QXL_CURSOR_MOVE  ] = "move",
   [ QXL_CURSOR_HIDE  ] = "hide",
   [ QXL_CURSOR_TRAIL ] = "trail",
};

static const char *spice_cursor_type[] = {
   [ SPICE_CURSOR_TYPE_ALPHA   ] = "alpha",
   [ SPICE_CURSOR_TYPE_MONO    ] = "mono",
   [ SPICE_CURSOR_TYPE_COLOR4  ] = "color4",
   [ SPICE_CURSOR_TYPE_COLOR8  ] = "color8",
   [ SPICE_CURSOR_TYPE_COLOR16 ] = "color16",
   [ SPICE_CURSOR_TYPE_COLOR24 ] = "color24",
   [ SPICE_CURSOR_TYPE_COLOR32 ] = "color32",
};

static const char *qxl_v2n(const char *n[], size_t l, int v)
{
    if (v >= l || !n[v]) {
        return "???";
    }
    return n[v];
}
#define qxl_name(_list, _value) qxl_v2n(_list, ARRAY_SIZE(_list), _value)

static void qxl_log_image(PCIQXLDevice *qxl, QXLPHYSICAL addr, int group_id)
{
    QXLImage *image;
    QXLImageDescriptor *desc;

    image = qxl_phys2virt(qxl, addr, group_id);
    desc = &image->descriptor;
    fprintf(stderr, " (id %" PRIx64 " type %d flags %d width %d height %d",
            desc->id, desc->type, desc->flags, desc->width, desc->height);
    switch (desc->type) {
    case SPICE_IMAGE_TYPE_BITMAP:
        fprintf(stderr, ", fmt %d flags %d x %d y %d stride %d"
                " palette %" PRIx64 " data %" PRIx64,
                image->bitmap.format, image->bitmap.flags,
                image->bitmap.x, image->bitmap.y,
                image->bitmap.stride,
                image->bitmap.palette, image->bitmap.data);
        break;
    }
    fprintf(stderr, ")");
}

static void qxl_log_rect(QXLRect *rect)
{
    fprintf(stderr, " %dx%d+%d+%d",
            rect->right - rect->left,
            rect->bottom - rect->top,
            rect->left, rect->top);
}

static void qxl_log_cmd_draw_copy(PCIQXLDevice *qxl, QXLCopy *copy, int group_id)
{
    fprintf(stderr, " src %" PRIx64,
            copy->src_bitmap);
    qxl_log_image(qxl, copy->src_bitmap, group_id);
    fprintf(stderr, " area");
    qxl_log_rect(&copy->src_area);
    fprintf(stderr, " rop %d", copy->rop_descriptor);
}

static void qxl_log_cmd_draw(PCIQXLDevice *qxl, QXLDrawable *draw, int group_id)
{
    fprintf(stderr, ": surface_id %d type %s effect %s",
            draw->surface_id,
            qxl_name(qxl_draw_type, draw->type),
            qxl_name(qxl_draw_effect, draw->effect));
    switch (draw->type) {
    case QXL_DRAW_COPY:
        qxl_log_cmd_draw_copy(qxl, &draw->u.copy, group_id);
        break;
    }
}

static void qxl_log_cmd_draw_compat(PCIQXLDevice *qxl, QXLCompatDrawable *draw,
                                    int group_id)
{
    fprintf(stderr, ": type %s effect %s",
            qxl_name(qxl_draw_type, draw->type),
            qxl_name(qxl_draw_effect, draw->effect));
    if (draw->bitmap_offset) {
        fprintf(stderr, ": bitmap %d",
                draw->bitmap_offset);
        qxl_log_rect(&draw->bitmap_area);
    }
    switch (draw->type) {
    case QXL_DRAW_COPY:
        qxl_log_cmd_draw_copy(qxl, &draw->u.copy, group_id);
        break;
    }
}

static void qxl_log_cmd_surface(PCIQXLDevice *qxl, QXLSurfaceCmd *cmd)
{
    fprintf(stderr, ": %s id %d",
            qxl_name(qxl_surface_cmd, cmd->type),
            cmd->surface_id);
    if (cmd->type == QXL_SURFACE_CMD_CREATE) {
        fprintf(stderr, " size %dx%d stride %d format %s (count %d, max %d)",
                cmd->u.surface_create.width,
                cmd->u.surface_create.height,
                cmd->u.surface_create.stride,
                qxl_name(spice_surface_fmt, cmd->u.surface_create.format),
                qxl->guest_surfaces.count, qxl->guest_surfaces.max);
    }
    if (cmd->type == QXL_SURFACE_CMD_DESTROY) {
        fprintf(stderr, " (count %d)", qxl->guest_surfaces.count);
    }
}

void qxl_log_cmd_cursor(PCIQXLDevice *qxl, QXLCursorCmd *cmd, int group_id)
{
    QXLCursor *cursor;

    fprintf(stderr, ": %s",
            qxl_name(qxl_cursor_cmd, cmd->type));
    switch (cmd->type) {
    case QXL_CURSOR_SET:
        fprintf(stderr, " +%d+%d visible %s, shape @ 0x%" PRIx64,
                cmd->u.set.position.x,
                cmd->u.set.position.y,
                cmd->u.set.visible ? "yes" : "no",
                cmd->u.set.shape);
        cursor = qxl_phys2virt(qxl, cmd->u.set.shape, group_id);
        fprintf(stderr, " type %s size %dx%d hot-spot +%d+%d"
                " unique 0x%" PRIx64 " data-size %d",
                qxl_name(spice_cursor_type, cursor->header.type),
                cursor->header.width, cursor->header.height,
                cursor->header.hot_spot_x, cursor->header.hot_spot_y,
                cursor->header.unique, cursor->data_size);
        break;
    case QXL_CURSOR_MOVE:
        fprintf(stderr, " +%d+%d", cmd->u.position.x, cmd->u.position.y);
        break;
    }
}

void qxl_log_command(PCIQXLDevice *qxl, const char *ring, QXLCommandExt *ext)
{
    bool compat = ext->flags & QXL_COMMAND_FLAG_COMPAT;
    void *data;

    if (!qxl->cmdlog) {
        return;
    }
    fprintf(stderr, "qxl-%d/%s:", qxl->id, ring);
    fprintf(stderr, " cmd @ 0x%" PRIx64 " %s%s", ext->cmd.data,
            qxl_name(qxl_type, ext->cmd.type),
            compat ? "(compat)" : "");

    data = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id);
    switch (ext->cmd.type) {
    case QXL_CMD_DRAW:
        if (!compat) {
            qxl_log_cmd_draw(qxl, data, ext->group_id);
        } else {
            qxl_log_cmd_draw_compat(qxl, data, ext->group_id);
        }
        break;
    case QXL_CMD_SURFACE:
        qxl_log_cmd_surface(qxl, data);
        break;
    case QXL_CMD_CURSOR:
        qxl_log_cmd_cursor(qxl, data, ext->group_id);
        break;
    }
    fprintf(stderr, "\n");
}

hw/qxl-render.c

0 → 100644
+226 −0
Original line number Diff line number Diff line
/*
 * qxl local rendering (aka display on sdl/vnc)
 *
 * Copyright (C) 2010 Red Hat, Inc.
 *
 * maintained by Gerd Hoffmann <kraxel@redhat.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 or
 * (at your option) version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include "qxl.h"

static void qxl_flip(PCIQXLDevice *qxl, QXLRect *rect)
{
    uint8_t *src = qxl->guest_primary.data;
    uint8_t *dst = qxl->guest_primary.flipped;
    int len, i;

    src += (qxl->guest_primary.surface.height - rect->top - 1) *
        qxl->guest_primary.stride;
    dst += rect->top  * qxl->guest_primary.stride;
    src += rect->left * qxl->guest_primary.bytes_pp;
    dst += rect->left * qxl->guest_primary.bytes_pp;
    len  = (rect->right - rect->left) * qxl->guest_primary.bytes_pp;

    for (i = rect->top; i < rect->bottom; i++) {
        memcpy(dst, src, len);
        dst += qxl->guest_primary.stride;
        src -= qxl->guest_primary.stride;
    }
}

void qxl_render_resize(PCIQXLDevice *qxl)
{
    QXLSurfaceCreate *sc = &qxl->guest_primary.surface;

    qxl->guest_primary.stride = sc->stride;
    qxl->guest_primary.resized++;
    switch (sc->format) {
    case SPICE_SURFACE_FMT_16_555:
        qxl->guest_primary.bytes_pp = 2;
        qxl->guest_primary.bits_pp = 15;
        break;
    case SPICE_SURFACE_FMT_16_565:
        qxl->guest_primary.bytes_pp = 2;
        qxl->guest_primary.bits_pp = 16;
        break;
    case SPICE_SURFACE_FMT_32_xRGB:
    case SPICE_SURFACE_FMT_32_ARGB:
        qxl->guest_primary.bytes_pp = 4;
        qxl->guest_primary.bits_pp = 32;
        break;
    default:
        fprintf(stderr, "%s: unhandled format: %x\n", __FUNCTION__,
                qxl->guest_primary.surface.format);
        qxl->guest_primary.bytes_pp = 4;
        qxl->guest_primary.bits_pp = 32;
        break;
    }
}

void qxl_render_update(PCIQXLDevice *qxl)
{
    VGACommonState *vga = &qxl->vga;
    QXLRect dirty[32], update;
    void *ptr;
    int i;

    if (qxl->guest_primary.resized) {
        qxl->guest_primary.resized = 0;

        if (qxl->guest_primary.flipped) {
            qemu_free(qxl->guest_primary.flipped);
            qxl->guest_primary.flipped = NULL;
        }
        qemu_free_displaysurface(vga->ds);

        qxl->guest_primary.data = qemu_get_ram_ptr(qxl->vga.vram_offset);
        if (qxl->guest_primary.stride < 0) {
            /* spice surface is upside down -> need extra buffer to flip */
            qxl->guest_primary.stride = -qxl->guest_primary.stride;
            qxl->guest_primary.flipped = qemu_malloc(qxl->guest_primary.surface.width *
                                                     qxl->guest_primary.stride);
            ptr = qxl->guest_primary.flipped;
        } else {
            ptr = qxl->guest_primary.data;
        }
        dprint(qxl, 1, "%s: %dx%d, stride %d, bpp %d, depth %d, flip %s\n",
               __FUNCTION__,
               qxl->guest_primary.surface.width,
               qxl->guest_primary.surface.height,
               qxl->guest_primary.stride,
               qxl->guest_primary.bytes_pp,
               qxl->guest_primary.bits_pp,
               qxl->guest_primary.flipped ? "yes" : "no");
        vga->ds->surface =
            qemu_create_displaysurface_from(qxl->guest_primary.surface.width,
                                            qxl->guest_primary.surface.height,
                                            qxl->guest_primary.bits_pp,
                                            qxl->guest_primary.stride,
                                            ptr);
        dpy_resize(vga->ds);
    }

    if (!qxl->guest_primary.commands) {
        return;
    }
    qxl->guest_primary.commands = 0;

    update.left   = 0;
    update.right  = qxl->guest_primary.surface.width;
    update.top    = 0;
    update.bottom = qxl->guest_primary.surface.height;

    memset(dirty, 0, sizeof(dirty));
    qxl->ssd.worker->update_area(qxl->ssd.worker, 0, &update,
                                 dirty, ARRAY_SIZE(dirty), 1);

    for (i = 0; i < ARRAY_SIZE(dirty); i++) {
        if (qemu_spice_rect_is_empty(dirty+i)) {
            break;
        }
        if (qxl->guest_primary.flipped) {
            qxl_flip(qxl, dirty+i);
        }
        dpy_update(vga->ds,
                   dirty[i].left, dirty[i].top,
                   dirty[i].right - dirty[i].left,
                   dirty[i].bottom - dirty[i].top);
    }
}

static QEMUCursor *qxl_cursor(PCIQXLDevice *qxl, QXLCursor *cursor)
{
    QEMUCursor *c;
    uint8_t *image, *mask;
    int size;

    c = cursor_alloc(cursor->header.width, cursor->header.height);
    c->hot_x = cursor->header.hot_spot_x;
    c->hot_y = cursor->header.hot_spot_y;
    switch (cursor->header.type) {
    case SPICE_CURSOR_TYPE_ALPHA:
        size = cursor->header.width * cursor->header.height * sizeof(uint32_t);
        memcpy(c->data, cursor->chunk.data, size);
        if (qxl->debug > 2) {
            cursor_print_ascii_art(c, "qxl/alpha");
        }
        break;
    case SPICE_CURSOR_TYPE_MONO:
        mask  = cursor->chunk.data;
        image = mask + cursor_get_mono_bpl(c) * c->width;
        cursor_set_mono(c, 0xffffff, 0x000000, image, 1, mask);
        if (qxl->debug > 2) {
            cursor_print_ascii_art(c, "qxl/mono");
        }
        break;
    default:
        fprintf(stderr, "%s: not implemented: type %d\n",
                __FUNCTION__, cursor->header.type);
        goto fail;
    }
    return c;

fail:
    cursor_put(c);
    return NULL;
}


/* called from spice server thread context only */
void qxl_render_cursor(PCIQXLDevice *qxl, QXLCommandExt *ext)
{
    QXLCursorCmd *cmd = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id);
    QXLCursor *cursor;
    QEMUCursor *c;
    int x = -1, y = -1;

    if (!qxl->ssd.ds->mouse_set || !qxl->ssd.ds->cursor_define) {
        return;
    }

    if (qxl->debug > 1 && cmd->type != QXL_CURSOR_MOVE) {
        fprintf(stderr, "%s", __FUNCTION__);
        qxl_log_cmd_cursor(qxl, cmd, ext->group_id);
        fprintf(stderr, "\n");
    }
    switch (cmd->type) {
    case QXL_CURSOR_SET:
        x = cmd->u.set.position.x;
        y = cmd->u.set.position.y;
        cursor = qxl_phys2virt(qxl, cmd->u.set.shape, ext->group_id);
        if (cursor->chunk.data_size != cursor->data_size) {
            fprintf(stderr, "%s: multiple chunks\n", __FUNCTION__);
            return;
        }
        c = qxl_cursor(qxl, cursor);
        if (c == NULL) {
            c = cursor_builtin_left_ptr();
        }
        qemu_mutex_lock_iothread();
        qxl->ssd.ds->cursor_define(c);
        qxl->ssd.ds->mouse_set(x, y, 1);
        qemu_mutex_unlock_iothread();
        cursor_put(c);
        break;
    case QXL_CURSOR_MOVE:
        x = cmd->u.position.x;
        y = cmd->u.position.y;
        qemu_mutex_lock_iothread();
        qxl->ssd.ds->mouse_set(x, y, 1);
        qemu_mutex_unlock_iothread();
        break;
    }
}
Loading