dm: virtio-gpu: virtual display support

ACRN virtual monitor shown as a GUI system window of service vm.
It can display graphic outputs of guest vm which is stored in
service vm's buffers by virtio-gpu. Display operation is accelerated
by Intel GPU PF(SRIOV) device with SDL(OpenGL ES 2.0 backend) API.
This provides one generic display solution. When the virtio-gpu is
added, it will firstly try to setup the connection to graphics system
and then display the framebuffer from the guest vm in the created
window region.

Tracked-On: #7210
Signed-off-by: Sun, Peng <peng.p.sun@linux.intel.com>
Reviewed-by: Zhao, yakui <yakui.zhao@intel.com>
Acked-by: Wang, Yu1 <yu1.wang@intel.com>
This commit is contained in:
Sun, Peng 2022-02-23 17:55:45 +08:00 committed by acrnsi-robot
parent 37a4cdc2e0
commit bca6464e9f
6 changed files with 672 additions and 0 deletions

View File

@ -39,6 +39,10 @@ CFLAGS += -I$(BASEDIR)/include
CFLAGS += -I$(BASEDIR)/include/public
CFLAGS += -I$(DM_OBJDIR)/include
CFLAGS += -I$(TOOLS_OUT)/services
CFLAGS += -I/usr/include/pixman-1
CFLAGS += -I/usr/include/glib-2.0
CFLAGS += -I/usr/include/SDL2
CFLAGS += -I/usr/include/EGL
ifneq (, $(DM_ASL_COMPILER))
CFLAGS += -DASL_COMPILER=\"$(DM_ASL_COMPILER)\"
@ -82,6 +86,9 @@ LIBS += -luuid
LIBS += -lusb-1.0
LIBS += -lacrn-mngr
LIBS += -lcjson
LIBS += -lpixman-1
LIBS += -lSDL2
LIBS += -lEGL
# lib
SRCS += lib/dm_string.c
@ -90,6 +97,7 @@ SRCS += lib/dm_string.c
SRCS += hw/block_if.c
SRCS += hw/usb_core.c
SRCS += hw/uart_core.c
SRCS += hw/vdisplay_sdl.c
SRCS += hw/pci/virtio/virtio.c
SRCS += hw/pci/virtio/virtio_kernel.c
SRCS += hw/pci/virtio/vhost.c

View File

@ -69,6 +69,7 @@
#include "pci_util.h"
#include "vssram.h"
#include "cmd_monitor.h"
#include "vdisplay.h"
#define VM_MAXCPU 16 /* maximum virtual cpus */
@ -98,6 +99,7 @@ bool ssram;
bool vtpm2;
bool is_winvm;
bool skip_pci_mem64bar_workaround = false;
bool gfx_ui = false;
static int guest_ncpus;
static int virtio_msix = 1;
@ -1038,6 +1040,10 @@ main(int argc, char *argv[])
exit(1);
}
if (gfx_ui) {
gfx_ui_init();
}
for (;;) {
pr_notice("vm_create: %s\n", vmname);
ctx = vm_create(vmname, (unsigned long)ioreq_buf, &guest_ncpus);
@ -1156,6 +1162,9 @@ fail:
create_fail:
if (cmd_monitor)
deinit_cmd_monitor();
if (gfx_ui) {
gfx_ui_deinit();
}
uninit_hugetlb();
deinit_loggers();
exit(ret);

View File

@ -47,6 +47,7 @@
#include "lpc.h"
#include "sw_load.h"
#include "log.h"
#include "vdisplay.h"
#define CONF1_ADDR_PORT 0x0cf8
#define CONF1_DATA_PORT 0x0cfc
@ -91,6 +92,7 @@ static uint64_t pci_emul_membase32;
static uint64_t pci_emul_membase64;
extern bool skip_pci_mem64bar_workaround;
extern bool gfx_ui;
struct io_rsvd_rgn reserved_bar_regions[REGION_NUMS];
@ -351,6 +353,13 @@ pci_parse_slot(char *opt)
vsbl_set_bdf(bnum, snum, fnum);
}
}
if ((strcmp("virtio-gpu", emul) == 0)) {
pr_info("%s: virtio-gpu device found, activating virtual display.\n",
__func__);
gfx_ui = true;
vdpy_parse_cmd_option(config);
}
done:
if (error)
free(str);

View File

@ -14,6 +14,7 @@
#include "dm.h"
#include "pci_core.h"
#include "virtio.h"
#include "vdisplay.h"
/*
* Queue definitions.
@ -120,6 +121,7 @@ struct virtio_gpu {
struct virtio_vq_info vq[VIRTIO_GPU_QNUM];
struct virtio_gpu_config cfg;
pthread_mutex_t mtx;
int vdpy_handle;
};
struct virtio_gpu_command {
@ -397,6 +399,7 @@ virtio_gpu_init(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
pr_err("%s, set modern bar failed.\n", __func__);
return rc;
}
gpu->vdpy_handle = vdpy_init();
return 0;
}
@ -412,6 +415,7 @@ virtio_gpu_deinit(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
free(gpu);
}
virtio_gpu_device_cnt--;
vdpy_deinit(gpu->vdpy_handle);
}
static void

View File

@ -0,0 +1,563 @@
/*
* Copyright (C) 2022 Intel Corporation.
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Virtual Display for VMs
*
*/
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <SDL.h>
#include <SDL_syswm.h>
#include <egl.h>
#include <pixman.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include "log.h"
#include "vdisplay.h"
#include "atomic.h"
#include "timer.h"
#define VDPY_MAX_WIDTH 1920
#define VDPY_MAX_HEIGHT 1080
#define VDPY_DEFAULT_WIDTH 1280
#define VDPY_DEFAULT_HEIGHT 720
#define VDPY_MIN_WIDTH 640
#define VDPY_MIN_HEIGHT 480
#define transto_10bits(color) (uint16_t)(color * 1024 + 0.5)
static unsigned char default_raw_argb[640 * 480 * 4];
struct state {
bool is_ui_realized;
bool is_active;
bool is_wayland;
bool is_x11;
bool is_fullscreen;
uint64_t updates;
int n_connect;
};
static struct display {
struct display_info info;
struct state s;
SDL_Texture *dpy_texture;
SDL_Window *dpy_win;
SDL_Renderer *dpy_renderer;
pixman_image_t *dpy_img;
pthread_t tid;
int width, height; // Width/height of dpy_win
int org_x, org_y;
int guest_width, guest_height;
int screen;
struct surface surf;
/* Add one UI_timer(33ms) to render the buffers from guest_vm */
struct acrn_timer ui_timer;
struct vdpy_display_bh ui_timer_bh;
/* Record the update_time that is activated from guest_vm */
struct timespec last_time;
// protect the request_list
pthread_mutex_t vdisplay_mutex;
// receive the signal that request is submitted
pthread_cond_t vdisplay_signal;
TAILQ_HEAD(display_list, vdpy_display_bh) request_list;
} vdpy = {
.s.is_ui_realized = false,
.s.is_active = false,
.s.is_wayland = false,
.s.is_x11 = false,
.s.is_fullscreen = false,
.s.updates = 0,
.s.n_connect = 0
};
void
vdpy_get_display_info(int handle, struct display_info *info)
{
if (handle == vdpy.s.n_connect) {
info->xoff = vdpy.info.xoff;
info->yoff = vdpy.info.yoff;
info->width = vdpy.info.width;
info->height = vdpy.info.height;
} else {
info->xoff = 0;
info->yoff = 0;
info->width = 0;
info->height = 0;
}
}
void
vdpy_surface_set(int handle, struct surface *surf)
{
pixman_image_t *src_img;
int format;
if (handle != vdpy.s.n_connect) {
return;
}
if (surf == NULL ) {
vdpy.surf.width = 0;
vdpy.surf.height = 0;
/* Need to use the default 640x480 for the SDL_Texture */
src_img = pixman_image_create_bits(PIXMAN_a8r8g8b8,
VDPY_MIN_WIDTH, VDPY_MIN_HEIGHT,
(uint32_t *)default_raw_argb,
VDPY_MIN_WIDTH * 4);
if (src_img == NULL) {
pr_err("failed to create pixman_image\n");
return;
}
vdpy.guest_width = VDPY_MIN_WIDTH;
vdpy.guest_height = VDPY_MIN_HEIGHT;
} else {
src_img = pixman_image_create_bits(surf->surf_format,
surf->width, surf->height, surf->pixel,
surf->stride);
if (src_img == NULL) {
pr_err("failed to create pixman_image\n");
return;
}
vdpy.surf = *surf;
vdpy.guest_width = surf->width;
vdpy.guest_height = surf->height;
}
if (vdpy.dpy_texture) {
SDL_DestroyTexture(vdpy.dpy_texture);
}
format = SDL_PIXELFORMAT_ARGB8888;
switch (pixman_image_get_format(src_img)) {
case PIXMAN_a8r8g8b8:
case PIXMAN_x8r8g8b8:
format = SDL_PIXELFORMAT_ARGB8888;
break;
case PIXMAN_a8b8g8r8:
case PIXMAN_x8b8g8r8:
format = SDL_PIXELFORMAT_ABGR8888;
break;
case PIXMAN_r8g8b8a8:
format = SDL_PIXELFORMAT_RGBA8888;
case PIXMAN_r8g8b8x8:
format = SDL_PIXELFORMAT_RGBX8888;
break;
case PIXMAN_b8g8r8a8:
case PIXMAN_b8g8r8x8:
format = SDL_PIXELFORMAT_BGRA8888;
break;
default:
pr_err("Unsupported format. %x\n",
pixman_image_get_format(src_img));
}
vdpy.dpy_texture = SDL_CreateTexture(vdpy.dpy_renderer,
format, SDL_TEXTUREACCESS_STREAMING,
vdpy.guest_width, vdpy.guest_height);
if (vdpy.dpy_texture == NULL) {
pr_err("Failed to create SDL_texture for surface.\n");
}
/* For the surf_switch, it will be updated in surface_update */
if (!surf) {
SDL_UpdateTexture(vdpy.dpy_texture, NULL,
pixman_image_get_data(src_img),
pixman_image_get_stride(src_img));
SDL_RenderClear(vdpy.dpy_renderer);
SDL_RenderCopy(vdpy.dpy_renderer, vdpy.dpy_texture, NULL, NULL);
SDL_RenderPresent(vdpy.dpy_renderer);
}
if (vdpy.dpy_img)
pixman_image_unref(vdpy.dpy_img);
if (surf == NULL) {
SDL_SetWindowTitle(vdpy.dpy_win,
"Not activate display yet!");
} else {
SDL_SetWindowTitle(vdpy.dpy_win,
"ACRN Virtual Monitor");
}
/* Replace the cur_img with the created_img */
vdpy.dpy_img = src_img;
}
void
vdpy_surface_update(int handle, struct surface *surf)
{
if (handle != vdpy.s.n_connect) {
return;
}
if (!surf) {
pr_err("Incorrect order of submitting Virtio-GPU cmd.\n");
return;
}
SDL_UpdateTexture(vdpy.dpy_texture, NULL,
surf->pixel,
surf->stride);
SDL_RenderClear(vdpy.dpy_renderer);
SDL_RenderCopy(vdpy.dpy_renderer, vdpy.dpy_texture, NULL, NULL);
SDL_RenderPresent(vdpy.dpy_renderer);
/* update the rendering time */
clock_gettime(CLOCK_MONOTONIC, &vdpy.last_time);
}
static void
vdpy_sdl_ui_refresh(void *data)
{
struct display *ui_vdpy;
struct timespec cur_time;
uint64_t elapsed_time;
ui_vdpy = (struct display *)data;
/* Skip it if no surface needs to be rendered */
if (ui_vdpy->dpy_texture == NULL)
return;
clock_gettime(CLOCK_MONOTONIC, &cur_time);
elapsed_time = (cur_time.tv_sec - ui_vdpy->last_time.tv_sec) * 1000000000 +
cur_time.tv_nsec - ui_vdpy->last_time.tv_nsec;
/* the time interval is less than 10ms. Skip it */
if (elapsed_time < 10000000)
return;
SDL_RenderClear(ui_vdpy->dpy_renderer);
SDL_RenderCopy(ui_vdpy->dpy_renderer, ui_vdpy->dpy_texture, NULL, NULL);
SDL_RenderPresent(ui_vdpy->dpy_renderer);
}
static void
vdpy_sdl_ui_timer(void *data, uint64_t nexp)
{
struct display *ui_vdpy;
struct vdpy_display_bh *bh_task;
ui_vdpy = (struct display *)data;
/* Don't submit the display_request if another func already
* acquires the mutex.
* This is to optimize the mevent thread otherwise it needs
* to wait for some time.
*/
if (pthread_mutex_trylock(&ui_vdpy->vdisplay_mutex))
return;
bh_task = &ui_vdpy->ui_timer_bh;
if ((bh_task->bh_flag & ACRN_BH_PENDING) == 0) {
bh_task->bh_flag |= ACRN_BH_PENDING;
TAILQ_INSERT_TAIL(&ui_vdpy->request_list, bh_task, link);
}
pthread_cond_signal(&ui_vdpy->vdisplay_signal);
pthread_mutex_unlock(&ui_vdpy->vdisplay_mutex);
}
static void *
vdpy_sdl_display_thread(void *data)
{
uint32_t win_flags;
struct vdpy_display_bh *bh;
struct itimerspec ui_timer_spec;
if (vdpy.width && vdpy.height) {
/* clip the region between (640x480) and (1920x1080) */
if (vdpy.width < VDPY_MIN_WIDTH)
vdpy.width = VDPY_MIN_WIDTH;
if (vdpy.width > VDPY_MAX_WIDTH)
vdpy.width = VDPY_MAX_WIDTH;
if (vdpy.height < VDPY_MIN_HEIGHT)
vdpy.height = VDPY_MIN_HEIGHT;
if (vdpy.height > VDPY_MAX_HEIGHT)
vdpy.height = VDPY_MAX_HEIGHT;
} else {
/* the default window(1280x720) is created with undefined pos
* when no geometry info is passed
*/
vdpy.org_x = 0xFFFF;
vdpy.org_y = 0xFFFF;
vdpy.width = VDPY_DEFAULT_WIDTH;
vdpy.height = VDPY_DEFAULT_HEIGHT;
}
win_flags = SDL_WINDOW_OPENGL |
SDL_WINDOW_ALWAYS_ON_TOP |
SDL_WINDOW_SHOWN;
if (vdpy.s.is_fullscreen) {
win_flags |= SDL_WINDOW_FULLSCREEN;
}
vdpy.dpy_win = NULL;
vdpy.dpy_renderer = NULL;
vdpy.dpy_win = SDL_CreateWindow("ACRN_DM",
vdpy.org_x, vdpy.org_y,
vdpy.width, vdpy.height,
win_flags);
if (vdpy.dpy_win == NULL) {
pr_err("Failed to Create SDL_Window\n");
goto sdl_fail;
}
vdpy.dpy_renderer = SDL_CreateRenderer(vdpy.dpy_win, -1, 0);
if (vdpy.dpy_renderer == NULL) {
pr_err("Failed to Create GL_Renderer \n");
goto sdl_fail;
}
pthread_mutex_init(&vdpy.vdisplay_mutex, NULL);
pthread_cond_init(&vdpy.vdisplay_signal, NULL);
TAILQ_INIT(&vdpy.request_list);
vdpy.s.is_active = 1;
vdpy.ui_timer_bh.task_cb = vdpy_sdl_ui_refresh;
vdpy.ui_timer_bh.data = &vdpy;
clock_gettime(CLOCK_MONOTONIC, &vdpy.last_time);
vdpy.ui_timer.clockid = CLOCK_MONOTONIC;
acrn_timer_init(&vdpy.ui_timer, vdpy_sdl_ui_timer, &vdpy);
ui_timer_spec.it_interval.tv_sec = 0;
ui_timer_spec.it_interval.tv_nsec = 33000000;
/* Wait for 5s to start the timer */
ui_timer_spec.it_value.tv_sec = 5;
ui_timer_spec.it_value.tv_nsec = 0;
/* Start one periodic timer to refresh UI based on 30fps */
acrn_timer_settime(&vdpy.ui_timer, &ui_timer_spec);
pr_info("SDL display thread is created\n");
/* Begin to process the display_cmd after initialization */
do {
if (!vdpy.s.is_active) {
pr_info("display is exiting\n");
break;
}
pthread_mutex_lock(&vdpy.vdisplay_mutex);
if (TAILQ_EMPTY(&vdpy.request_list))
pthread_cond_wait(&vdpy.vdisplay_signal,
&vdpy.vdisplay_mutex);
/* the bh_task is handled in vdisplay_mutex lock */
while (!TAILQ_EMPTY(&vdpy.request_list)) {
bh = TAILQ_FIRST(&vdpy.request_list);
TAILQ_REMOVE(&vdpy.request_list, bh, link);
bh->task_cb(bh->data);
if (atomic_load(&bh->bh_flag) & ACRN_BH_FREE) {
free(bh);
bh = NULL;
} else {
/* free is owned by the submitter */
atomic_store(&bh->bh_flag, ACRN_BH_DONE);
}
}
pthread_mutex_unlock(&vdpy.vdisplay_mutex);
} while (1);
acrn_timer_deinit(&vdpy.ui_timer);
/* SDL display_thread will exit because of DM request */
pthread_mutex_destroy(&vdpy.vdisplay_mutex);
pthread_cond_destroy(&vdpy.vdisplay_signal);
if (vdpy.dpy_img)
pixman_image_unref(vdpy.dpy_img);
/* Continue to thread cleanup */
if (vdpy.dpy_texture) {
SDL_DestroyTexture(vdpy.dpy_texture);
vdpy.dpy_texture = NULL;
}
sdl_fail:
if (vdpy.dpy_renderer) {
SDL_DestroyRenderer(vdpy.dpy_renderer);
vdpy.dpy_renderer = NULL;
}
if (vdpy.dpy_win) {
SDL_DestroyWindow(vdpy.dpy_win);
vdpy.dpy_win = NULL;
}
/* This is used to workaround the TLS issue of libEGL + libGLdispatch
* after unloading library.
*/
eglReleaseThread();
return NULL;
}
bool vdpy_submit_bh(int handle, struct vdpy_display_bh *bh_task)
{
bool bh_ok = false;
if (handle != vdpy.s.n_connect) {
return bh_ok;
}
if (!vdpy.s.is_active)
return bh_ok;
pthread_mutex_lock(&vdpy.vdisplay_mutex);
if ((bh_task->bh_flag & ACRN_BH_PENDING) == 0) {
bh_task->bh_flag |= ACRN_BH_PENDING;
TAILQ_INSERT_TAIL(&vdpy.request_list, bh_task, link);
bh_ok = true;
}
pthread_cond_signal(&vdpy.vdisplay_signal);
pthread_mutex_unlock(&vdpy.vdisplay_mutex);
return bh_ok;
}
int
vdpy_init()
{
int err, count;
if (vdpy.s.n_connect) {
return 0;
}
/* start one vdpy_sdl_display_thread to handle the 3D request
* in this dedicated thread. Otherwise the libSDL + 3D doesn't
* work.
*/
err = pthread_create(&vdpy.tid, NULL, vdpy_sdl_display_thread, &vdpy);
if (err) {
pr_err("Failed to create the sdl_display_thread.\n");
return 0;
}
count = 0;
/* Wait up to 200ms so that the vdpy_sdl_display_thread is ready to
* handle the 3D request
*/
while (!vdpy.s.is_active && count < 20) {
usleep(10000);
count++;
}
if (!vdpy.s.is_active) {
pr_err("display_thread is not ready.\n");
}
vdpy.s.n_connect++;
return vdpy.s.n_connect;
}
int
vdpy_deinit(int handle)
{
if (handle != vdpy.s.n_connect) {
return -1;
}
vdpy.s.n_connect--;
if (!vdpy.s.is_active) {
return -1;
}
pthread_mutex_lock(&vdpy.vdisplay_mutex);
vdpy.s.is_active = 0;
/* Wakeup the vdpy_sdl_display_thread if it is waiting for signal */
pthread_cond_signal(&vdpy.vdisplay_signal);
pthread_mutex_unlock(&vdpy.vdisplay_mutex);
pthread_join(vdpy.tid, NULL);
pr_info("Exit SDL display thread\n");
return 0;
}
void
gfx_ui_init()
{
SDL_SysWMinfo info;
SDL_Rect disp_rect;
setenv("SDL_VIDEO_X11_FORCE_EGL", "1", 1);
setenv("SDL_OPENGL_ES_DRIVER", "1", 1);
setenv("SDL_RENDER_DRIVER", "opengles2", 1);
setenv("SDL_RENDER_SCALE_QUALITY", "linear", 1);
if (SDL_Init(SDL_INIT_VIDEO)) {
pr_err("Failed to Init SDL2 system");
}
SDL_GetDisplayBounds(0, &disp_rect);
if (disp_rect.w < VDPY_MIN_WIDTH ||
disp_rect.h < VDPY_MIN_HEIGHT) {
pr_err("Too small resolutions. Please check the "
" graphics system\n");
SDL_Quit();
}
SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1");
memset(&info, 0, sizeof(info));
SDL_VERSION(&info.version);
/* Set the GL_parameter for Window/Renderer */
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
SDL_GL_CONTEXT_PROFILE_ES);
/* GLES2.0 is used */
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
/* GL target surface selects A8/R8/G8/B8 */
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
vdpy.s.is_ui_realized = true;
}
void
gfx_ui_deinit()
{
if (!vdpy.s.is_ui_realized) {
return;
}
SDL_Quit();
pr_info("SDL_Quit\r\n");
}
int vdpy_parse_cmd_option(const char *opts)
{
char *str;
int snum, error;
error = 0;
str = strcasestr(opts, "geometry=");
if (opts && strcasestr(opts, "geometry=fullscreen")) {
snum = sscanf(str, "geometry=fullscreen:%d", &vdpy.screen);
if (snum != 1) {
vdpy.screen = 0;
}
vdpy.width = VDPY_MAX_WIDTH;
vdpy.height = VDPY_MAX_HEIGHT;
vdpy.s.is_fullscreen = true;
pr_info("virtual display: fullscreen.\n");
} else if (opts && strcasestr(opts, "geometry=")) {
snum = sscanf(str, "geometry=%dx%d+%d+%d",
&vdpy.width, &vdpy.height,
&vdpy.org_x, &vdpy.org_y);
if (snum != 4) {
pr_err("incorrect geometry option. Should be"
" WxH+x+y\n");
error = -1;
}
vdpy.s.is_fullscreen = false;
pr_info("virtual display: windowed.\n");
}
return error;
}

View File

@ -0,0 +1,79 @@
/*
* Copyright (C) 2022 Intel Corporation.
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Vistual Display for VMs
*
*/
#ifndef _VDISPLAY_H_
#define _VDISPLAY_H_
#include <sys/queue.h>
#include <pixman.h>
#include "dm.h"
typedef void (*bh_task_func)(void *data);
/* bh task is still pending */
#define ACRN_BH_PENDING (1 << 0)
/* bh task is done */
#define ACRN_BH_DONE (1 << 1)
/* free vdpy_display_bh after executing bh_cb */
#define ACRN_BH_FREE (1 << 2)
struct vdpy_display_bh {
TAILQ_ENTRY(vdpy_display_bh) link;
bh_task_func task_cb;
void *data;
uint32_t bh_flag;
};
struct edid_info {
char *vendor;
char *name;
char *sn;
uint32_t prefx;
uint32_t prefy;
uint32_t maxx;
uint32_t maxy;
uint32_t refresh_rate;
};
struct display_info {
/* geometry */
int xoff;
int yoff;
uint32_t width;
uint32_t height;
};
enum surface_type {
SURFACE_PIXMAN = 1,
SURFACE_DMABUF,
};
struct surface {
enum surface_type surf_type;
/* use pixman_format as the intermediate-format */
pixman_format_code_t surf_format;
uint32_t x;
uint32_t y;
uint32_t width;
uint32_t height;
uint32_t bpp;
uint32_t stride;
void *pixel;
};
int vdpy_parse_cmd_option(const char *opts);
void gfx_ui_init();
int vdpy_init();
void vdpy_get_display_info(int handle, struct display_info *info);
void vdpy_surface_set(int handle, struct surface *surf);
void vdpy_surface_update(int handle, struct surface *surf);
bool vdpy_submit_bh(int handle, struct vdpy_display_bh *bh);
int vdpy_deinit(int handle);
void gfx_ui_deinit();
#endif /* _VDISPLAY_H_ */