Files
acrn-hypervisor/devicemodel/hw/vdisplay_sdl.c
Sun, Peng f2cfa761ae dm: virtio-gpu: cursor surpport
Hardware cursor emulation of virtio-gpu video adapter. Guest vm can
show its own cursor in virtual display, not share the cursor with
service vm. It also accelerated by SDL(OpenGL ES 2.0 backend) API
with hardware acceleration based on the GPU hardware own by service
vm.

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>
2022-03-28 15:26:20 +08:00

1089 lines
27 KiB
C

/*
* 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;
struct cursor cur;
SDL_Texture *cursor_tex;
/* 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
};
typedef enum {
ESTT = 1, // Established Timings I & II
STDT, // Standard Timings
ESTT3, // Established Timings III
} TIMING_MODE;
static const struct timing_entry {
uint32_t hpixel;// Horizontal pixels
uint32_t vpixel;// Vertical pixels
uint32_t byte; // byte idx in the Established Timings I & II
uint32_t byte_t3;// byte idx in the Established Timings III Descriptor
uint32_t bit; // bit idx
uint8_t hz; // frequency
} timings[] = {
/* Established Timings I & II (all @ 60Hz) */
{ .hpixel = 1024, .vpixel = 768, .byte = 36, .bit = 3, .hz = 60},
{ .hpixel = 800, .vpixel = 600, .byte = 35, .bit = 0, .hz = 60 },
{ .hpixel = 640, .vpixel = 480, .byte = 35, .bit = 5, .hz = 60 },
/* Standard Timings */
{ .hpixel = 1920, .vpixel = 1080, .hz = 60 },
{ .hpixel = 1280, .vpixel = 720, .hz = 60 },
};
typedef struct frame_param{
uint32_t hav_pixel; // Horizontal Addressable Video in pixels
uint32_t hb_pixel; // Horizontal Blanking in pixels
uint32_t hfp_pixel; // Horizontal Front Porch in pixels
uint32_t hsp_pixel; // Horizontal Sync Pulse Width in pixels
uint32_t lhb_pixel; // Left Horizontal Border or Right Horizontal
// Border in pixels
uint32_t vav_line; // Vertical Addressable Video in lines
uint32_t vb_line; // Vertical Blanking in lines
uint32_t vfp_line; // Vertical Front Porch in Lines
uint32_t vsp_line; // Vertical Sync Pulse Width in Lines
uint32_t tvb_line; // Top Vertical Border or Bottom Vertical
// Border in Lines
uint64_t pixel_clock; // Hz
uint32_t width; // mm
uint32_t height; // mm
}frame_param;
typedef struct base_param{
uint32_t h_pixel; // pixels
uint32_t v_pixel; // lines
uint32_t h_pixelmax;
uint32_t v_pixelmax;
uint32_t rate; // Hz
uint32_t width; // mm
uint32_t height; // mm
const char *id_manuf; // ID Manufacturer Name, ISA 3-character ID Code
uint16_t id_product; // ID Product Code
uint32_t id_sn; // ID Serial Number and it is a number only.
const char *sn; // Serial number.
const char *product_name;// Product name.
}base_param;
static void
vdpy_edid_set_baseparam(base_param *b_param, uint32_t width, uint32_t height)
{
b_param->h_pixel = width;
b_param->v_pixel = height;
b_param->h_pixelmax = 0;
b_param->v_pixelmax = 0;
b_param->rate = 60;
b_param->width = width;
b_param->height = height;
b_param->id_manuf = "ACRN";
b_param->id_product = 4321;
b_param->id_sn = 12345678;
b_param->sn = "A0123456789";
b_param->product_name = "ACRN_Monitor";
}
static void
vdpy_edid_set_frame(frame_param *frame, const base_param *b_param)
{
frame->hav_pixel = b_param->h_pixel;
frame->hb_pixel = b_param->h_pixel * 35 / 100;
frame->hfp_pixel = b_param->h_pixel * 25 / 100;
frame->hsp_pixel = b_param->h_pixel * 3 / 100;
frame->lhb_pixel = 0;
frame->vav_line = b_param->v_pixel;
frame->vb_line = b_param->v_pixel * 35 / 1000;
frame->vfp_line = b_param->v_pixel * 5 / 1000;
frame->vsp_line = b_param->v_pixel * 5 / 1000;
frame->tvb_line = 0;
frame->pixel_clock = b_param->rate *
(frame->hav_pixel + frame->hb_pixel + frame->lhb_pixel * 2) *
(frame->vav_line + frame->vb_line + frame->tvb_line * 2);
frame->width = b_param->width;
frame->height = b_param->height;
}
static void
vdpy_edid_set_color(uint8_t *edid, float red_x, float red_y,
float green_x, float green_y,
float blue_x, float blue_y,
float white_x, float white_y)
{
uint8_t *color;
uint16_t rx, ry, gx, gy, bx, by, wx, wy;
rx = transto_10bits(red_x);
ry = transto_10bits(red_y);
gx = transto_10bits(green_x);
gy = transto_10bits(green_y);
bx = transto_10bits(blue_x);
by = transto_10bits(blue_y);
wx = transto_10bits(white_x);
wy = transto_10bits(white_y);
color = edid + 25;
color[0] = ((rx & 0x03) << 6) |
((ry & 0x03) << 4) |
((gx & 0x03) << 2) |
(gy & 0x03);
color[1] = ((bx & 0x03) << 6) |
((by & 0x03) << 4) |
((wx & 0x03) << 2) |
(wy & 0x03);
color[2] = rx >> 2;
color[3] = ry >> 2;
color[4] = gx >> 2;
color[5] = gy >> 2;
color[6] = bx >> 2;
color[7] = by >> 2;
color[8] = wx >> 2;
color[9] = wy >> 2;
}
static void
vdpy_edid_set_timing(uint8_t *addr, const base_param *b_param, TIMING_MODE mode)
{
static uint16_t idx;
static uint16_t size;
const struct timing_entry *timing;
uint8_t stdcnt;
uint16_t hpixel;
int16_t AR;
stdcnt = 0;
if(mode == STDT) {
addr += 38;
}
idx = 0;
size = sizeof(timings) / sizeof(timings[0]);
for(; idx < size; idx++){
timing = timings + idx;
if ((b_param->h_pixelmax && b_param->h_pixelmax < timing->hpixel) ||
(b_param->v_pixelmax && b_param->v_pixelmax < timing->vpixel)) {
continue;
}
switch(mode){
case ESTT: // Established Timings I & II
if(timing->byte) {
addr[timing->byte] |= (1 << timing->bit);
break;
} else {
return;
}
case ESTT3: // Established Timings III
if(timing->byte_t3){
addr[timing->byte_t3] |= (1 << timing->bit);
break;
} else {
return;
}
case STDT: // Standard Timings
if(stdcnt < 8 && (timing->hpixel == b_param->h_pixel)) {
hpixel = (timing->hpixel >> 3) - 31;
if (timing->hpixel == 0 ||
timing->vpixel == 0) {
AR = -1;
} else if (hpixel & 0xff00) {
AR = -2;
} else if (timing->hpixel * 10 ==
timing->vpixel * 16) {
AR = 0;
} else if (timing->hpixel * 3 ==
timing->vpixel * 4) {
AR = 1;
} else if (timing->hpixel * 4 ==
timing->vpixel * 5) {
AR = 2;
} else if (timing->hpixel * 9 ==
timing->vpixel * 16) {
AR = 3;
} else {
AR = -2;
}
if (AR >= 0) {
addr[0] = hpixel & 0xff;
addr[1] = (AR << 6) | ((timing->hz - 60) & 0x3f);
addr += 2;
stdcnt++;
} else if (AR == -1){
addr[0] = 0x01;
addr[1] = 0x01;
addr += 2;
stdcnt++;
}
break;
} else {
return;
}
default:
return;
}
}
while(mode == STDT && stdcnt < 8){
addr[0] = 0x01;
addr[1] = 0x01;
addr += 2;
stdcnt++;
}
}
static void
vdpy_edid_set_dtd(uint8_t *dtd, const frame_param *frame)
{
uint16_t pixel_clk;
// Range: 10 kHz to 655.35 MHz in 10 kHz steps
pixel_clk = frame->pixel_clock / 10000;
memcpy(dtd, &pixel_clk, sizeof(pixel_clk));
dtd[2] = frame->hav_pixel & 0xff;
dtd[3] = frame->hb_pixel & 0xff;
dtd[4] = ((frame->hav_pixel & 0xf00) >> 4) |
((frame->hb_pixel & 0xf00) >> 8);
dtd[5] = frame->vav_line & 0xff;
dtd[6] = frame->vb_line & 0xff;
dtd[7] = ((frame->vav_line & 0xf00) >> 4) |
((frame->vb_line & 0xf00) >> 8);
dtd[8] = frame->hfp_pixel & 0xff;
dtd[9] = frame->hsp_pixel & 0xff;
dtd[10] = ((frame->vfp_line & 0xf) << 4) |
(frame->vsp_line & 0xf);
dtd[11] = ((frame->hfp_pixel & 0x300) >> 2) |
((frame->hsp_pixel & 0x300) >> 4) |
((frame->vfp_line & 0x030) >> 6) |
((frame->vsp_line & 0x030) >> 8);
dtd[12] = frame->width & 0xff;
dtd[13] = frame->height & 0xff;
dtd[14] = ((frame->width & 0xf00) >> 4) |
((frame->height & 0xf00) >> 8);
dtd[15] = frame->lhb_pixel & 0xff;
dtd[16] = frame->tvb_line & 0xff;
dtd[17] = 0x18;
}
static void
vdpy_edid_set_descripter(uint8_t *edid, uint8_t is_dtd,
uint8_t tag, const base_param *b_param)
{
static uint8_t offset;
uint8_t *desc;
frame_param frame;
const char* text;
uint16_t len;
offset = 54;
desc = edid + offset;
if (is_dtd) {
vdpy_edid_set_frame(&frame, b_param);
vdpy_edid_set_dtd(desc, &frame);
offset += 18;
return;
}
desc[3] = tag;
text = NULL;
switch(tag){
// Established Timings III Descriptor (tag #F7h)
case 0xf7:
desc[5] = 0x0a; // Revision Number
vdpy_edid_set_timing(desc, b_param, ESTT3);
break;
// Display Range Limits & Additional Timing Descriptor (tag #FDh)
case 0xfd:
desc[5] = 50; // Minimum Vertical Rate. (50 -> 125 Hz)
desc[6] = 125; // Maximum Vertical Rate.
desc[7] = 30; // Minimum Horizontal Rate.(30 -> 160 kHz)
desc[8] = 160; // Maximum Horizontal Rate.
desc[9] = 2550 / 10; // Max Pixel Clock. (2550 MHz)
desc[10] = 0x01; // no extended timing information
desc[11] = '\n'; // padding
break;
// Display Product Name (ASCII) String Descriptor (tag #FCh)
case 0xfc:
// Display Product Serial Number Descriptor (tag #FFh)
case 0xff:
text = (tag == 0xff) ? b_param->sn : b_param->product_name;
memset(desc + 5, ' ', 13);
if (text == NULL)
break;
len = strlen(text);
if (len > 12)
len = 12;
memcpy(desc + 5, text, len);
desc[len + 5] = '\n';
break;
// Dummy Descriptor (Tag #10h)
case 0x10:
default:
break;
}
offset += 18;
}
static uint8_t
vdpy_edid_get_checksum(uint8_t *edid)
{
uint8_t sum;
int i;
sum = 0;
for (i = 0; i < 127; i++) {
sum += edid[i];
}
return 0x100 - sum;
}
static void
vdpy_edid_generate(uint8_t *edid, size_t size, struct edid_info *info)
{
uint16_t id_manuf;
uint16_t id_product;
uint32_t serial;
base_param b_param, c_param;
vdpy_edid_set_baseparam(&b_param, info->prefx, info->prefy);
memset(edid, 0, size);
/* edid[7:0], fixed header information, (00 FF FF FF FF FF FF 00)h */
memset(edid + 1, 0xff, 6);
/* edid[17:8], Vendor & Product Identification */
// Manufacturer ID is a big-endian 16-bit value.
id_manuf = ((((b_param.id_manuf[0] - '@') & 0x1f) << 10) |
(((b_param.id_manuf[1] - '@') & 0x1f) << 5) |
(((b_param.id_manuf[2] - '@') & 0x1f) << 0));
edid[8] = id_manuf >> 8;
edid[9] = id_manuf & 0xff;
// Manufacturer product code is a little-endian 16-bit number.
id_product = b_param.id_product;
memcpy(edid+10, &id_product, sizeof(id_product));
// Serial number is a little-endian 32-bit value.
serial = b_param.id_sn;
memcpy(edid+12, &serial, sizeof(serial));
edid[16] = 0; // Week of Manufacture
edid[17] = 2018 - 1990; // Year of Manufacture or Model Year.
// Acrn is released in 2018.
edid[18] = 1; // Version Number
edid[19] = 4; // Revision Number
/* edid[24:20], Basic Display Parameters & Features */
// Video Input Definition: 1 Byte
edid[20] = 0xa5; // Digital input;
// 8 Bits per Primary Color;
// DisplayPort is supported
// Horizontal and Vertical Screen Size or Aspect Ratio: 2 Bytes
// screen size, in centimetres
edid[21] = info->prefx / 10;
edid[22] = info->prefy / 10;
// Display Transfer Characteristics (GAMMA): 1 Byte
// Stored Value = (GAMMA x 100) - 100
edid[23] = 120; // display gamma: 2.2
// Feature Support: 1 Byte
edid[24] = 0x06; // sRGB Standard is the default color space;
// Preferred Timing Mode includes the native
// pixel format and preferred.
/* edid[34:25], Display x, y Chromaticity Coordinates */
vdpy_edid_set_color(edid, 0.6400, 0.3300,
0.3000, 0.6000,
0.1500, 0.0600,
0.3127, 0.3290);
/* edid[37:35], Established Timings */
vdpy_edid_set_timing(edid, &b_param, ESTT);
/* edid[53:38], Standard Timings: Identification 1 -> 8 */
vdpy_edid_set_timing(edid, &b_param, STDT);
/* edid[125:54], Detailed Timing Descriptor - 18 bytes x 4 */
// Detailed Timing Descriptor 1
vdpy_edid_set_baseparam(&c_param, VDPY_MAX_WIDTH, VDPY_MAX_HEIGHT);
vdpy_edid_set_descripter(edid, 0x1, 0, &c_param);
// Detailed Timing Descriptor 2
vdpy_edid_set_baseparam(&c_param, VDPY_DEFAULT_WIDTH, VDPY_DEFAULT_HEIGHT);
vdpy_edid_set_descripter(edid, 0x1, 0, &c_param);
// Display Product Name (ASCII) String Descriptor (tag #FCh)
vdpy_edid_set_descripter(edid, 0, 0xfc, &b_param);
// Display Product Serial Number Descriptor (tag #FFh)
vdpy_edid_set_descripter(edid, 0, 0xff, &b_param);
/* EDID[126], Extension Block Count */
edid[126] = 0; // no Extension Block
/* Checksum */
edid[127] = vdpy_edid_get_checksum(edid);
}
void
vdpy_get_edid(int handle, uint8_t *edid, size_t size)
{
struct edid_info edid_info;
if (handle == vdpy.s.n_connect) {
edid_info.prefx = vdpy.info.width;
edid_info.prefy = vdpy.info.height;
edid_info.maxx = VDPY_MAX_WIDTH;
edid_info.maxy = VDPY_MAX_HEIGHT;
} else {
edid_info.prefx = 0;
edid_info.prefy = 0;
edid_info.maxx = 0;
edid_info.maxy = 0;
}
edid_info.refresh_rate = 0;
edid_info.vendor = NULL;
edid_info.name = NULL;
edid_info.sn = NULL;
vdpy_edid_generate(edid, size, &edid_info);
}
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_cursor_position_transformation(struct display *vdpy, SDL_Rect *rect)
{
rect->x = (vdpy->cur.x * vdpy->width) / vdpy->guest_width;
rect->y = (vdpy->cur.y * vdpy->height) / vdpy->guest_height;
rect->w = (vdpy->cur.width * vdpy->width) / vdpy->guest_width;
rect->h = (vdpy->cur.height * vdpy->height) / vdpy->guest_height;
}
void
vdpy_surface_update(int handle, struct surface *surf)
{
SDL_Rect cursor_rect;
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);
/* This should be handled after rendering the surface_texture.
* Otherwise it will be hidden
*/
if (vdpy.cursor_tex) {
vdpy_cursor_position_transformation(&vdpy, &cursor_rect);
SDL_RenderCopy(vdpy.dpy_renderer, vdpy.cursor_tex,
NULL, &cursor_rect);
}
SDL_RenderPresent(vdpy.dpy_renderer);
/* update the rendering time */
clock_gettime(CLOCK_MONOTONIC, &vdpy.last_time);
}
void
vdpy_cursor_define(int handle, struct cursor *cur)
{
if (handle != vdpy.s.n_connect) {
return;
}
if (cur->data == NULL)
return;
if (vdpy.cursor_tex)
SDL_DestroyTexture(vdpy.cursor_tex);
vdpy.cursor_tex = SDL_CreateTexture(
vdpy.dpy_renderer,
SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING,
cur->width, cur->height);
if (vdpy.cursor_tex == NULL) {
pr_err("Failed to create sdl_cursor surface for %p.\n", cur);
return;
}
SDL_SetTextureBlendMode(vdpy.cursor_tex, SDL_BLENDMODE_BLEND);
vdpy.cur = *cur;
SDL_UpdateTexture(vdpy.cursor_tex, NULL, cur->data, cur->width * 4);
}
void
vdpy_cursor_move(int handle, uint32_t x, uint32_t y)
{
if (handle != vdpy.s.n_connect) {
return;
}
/* Only move the position of the cursor. The cursor_texture
* will be handled in surface_update
*/
vdpy.cur.x = x;
vdpy.cur.y = y;
}
static void
vdpy_sdl_ui_refresh(void *data)
{
struct display *ui_vdpy;
struct timespec cur_time;
uint64_t elapsed_time;
SDL_Rect cursor_rect;
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);
/* This should be handled after rendering the surface_texture.
* Otherwise it will be hidden
*/
if (ui_vdpy->cursor_tex) {
vdpy_cursor_position_transformation(ui_vdpy, &cursor_rect);
SDL_RenderCopy(ui_vdpy->dpy_renderer, ui_vdpy->cursor_tex,
NULL, &cursor_rect);
}
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;
}
if (vdpy.cursor_tex) {
SDL_DestroyTexture(vdpy.cursor_tex);
vdpy.cursor_tex = 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;
}