diff --git a/devicemodel/Makefile b/devicemodel/Makefile index b4b135818..a8c5b227f 100644 --- a/devicemodel/Makefile +++ b/devicemodel/Makefile @@ -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 diff --git a/devicemodel/core/main.c b/devicemodel/core/main.c index 384808bdd..de2e38b81 100644 --- a/devicemodel/core/main.c +++ b/devicemodel/core/main.c @@ -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); diff --git a/devicemodel/hw/pci/core.c b/devicemodel/hw/pci/core.c index 75c31f8ce..7f4a216b7 100644 --- a/devicemodel/hw/pci/core.c +++ b/devicemodel/hw/pci/core.c @@ -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); diff --git a/devicemodel/hw/pci/virtio/virtio_gpu.c b/devicemodel/hw/pci/virtio/virtio_gpu.c index 4b4f47e9f..242967fb6 100644 --- a/devicemodel/hw/pci/virtio/virtio_gpu.c +++ b/devicemodel/hw/pci/virtio/virtio_gpu.c @@ -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 diff --git a/devicemodel/hw/vdisplay_sdl.c b/devicemodel/hw/vdisplay_sdl.c new file mode 100644 index 000000000..c228b7244 --- /dev/null +++ b/devicemodel/hw/vdisplay_sdl.c @@ -0,0 +1,563 @@ +/* + * Copyright (C) 2022 Intel Corporation. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Virtual Display for VMs + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/devicemodel/include/vdisplay.h b/devicemodel/include/vdisplay.h new file mode 100644 index 000000000..07e71d036 --- /dev/null +++ b/devicemodel/include/vdisplay.h @@ -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 +#include +#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_ */