acrn-hypervisor/devicemodel/hw/vdisplay_sdl.c
Zhao Yakui 92034e9f7d ACRN:DM:VGPU: Add more check to avoid no geometry parameter for virtio-gpu
When no geometry parameter is passed for virtio-gpu, it should not be started.

Tracked-On: #7988
Signed-off-by: Zhao Yakui <yakui.zhao@intel.com>
Reviewed-by: Sun Peng <peng.p.sun@linux.intel.com>
Reviewed-by: Wang Yu <yu1.wang@intel.com>
2022-08-16 15:29:27 +08:00

1416 lines
36 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"
#include <egl.h>
#include <eglext.h>
#include <gl2.h>
#include <gl2ext.h>
#define VDPY_MAX_WIDTH 1920
#define VDPY_MAX_HEIGHT 1080
#define VDPY_DEFAULT_WIDTH 1024
#define VDPY_DEFAULT_HEIGHT 768
#define VDPY_MIN_WIDTH 640
#define VDPY_MIN_HEIGHT 480
#define transto_10bits(color) (uint16_t)(color * 1024 + 0.5)
#define VSCREEN_MAX_NUM 2
static unsigned char default_raw_argb[VDPY_DEFAULT_WIDTH * VDPY_DEFAULT_HEIGHT * 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;
};
struct egl_display_ops {
PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR;
PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR;
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
};
struct vscreen {
struct display_info info;
int pscreen_id;
SDL_Rect pscreen_rect;
bool is_fullscreen;
int org_x;
int org_y;
int width;
int height;
int guest_width;
int guest_height;
struct surface surf;
struct cursor cur;
SDL_Texture *surf_tex;
SDL_Texture *cur_tex;
SDL_Texture *bogus_tex;
int surf_updates;
int cur_updates;
SDL_Window *win;
SDL_Renderer *renderer;
pixman_image_t *img;
EGLImage egl_img;
/* Record the update_time that is activated from guest_vm */
struct timespec last_time;
};
static struct display {
struct state s;
struct vscreen *vscrs;
int vscrs_num;
pthread_t tid;
/* Add one UI_timer(33ms) to render the buffers from guest_vm */
struct acrn_timer ui_timer;
struct vdpy_display_bh ui_timer_bh;
// 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;
/* add the below two fields for calling eglAPI directly */
bool egl_dmabuf_supported;
SDL_GLContext eglContext;
EGLDisplay eglDisplay;
struct egl_display_ops gl_ops;
} vdpy = {
.s.is_ui_realized = false,
.s.is_active = false,
.s.is_wayland = false,
.s.is_x11 = false,
.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
bool is_std; // the flag of standard mode
} timings[] = {
/* Established Timings I & II (all @ 60Hz) */
{ .hpixel = 1280, .vpixel = 1024, .byte = 36, .bit = 0, .hz = 75},
{ .hpixel = 1024, .vpixel = 768, .byte = 36, .bit = 1, .hz = 75},
{ .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, .is_std = true },
{ .hpixel = 1680, .vpixel = 1050, .hz = 60, .is_std = true },
{ .hpixel = 1600, .vpixel = 1200, .hz = 60, .is_std = true },
{ .hpixel = 1600, .vpixel = 900, .hz = 60, .is_std = true },
{ .hpixel = 1440, .vpixel = 900, .hz = 60, .is_std = true },
};
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 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->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, 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;
switch(mode){
case ESTT: // Established Timings I & II
if(timing->byte) {
addr[timing->byte] |= (1 << timing->bit);
break;
} else {
continue;
}
case ESTT3: // Established Timings III
if(timing->byte_t3){
addr[timing->byte_t3] |= (1 << timing->bit);
break;
} else {
continue;
}
case STDT: // Standard Timings
if(stdcnt < 8 && timing->is_std) {
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 {
continue;
}
break;
default:
continue;
}
}
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 *desc, uint8_t is_dtd,
uint8_t tag, const base_param *b_param)
{
frame_param frame;
const char* text;
uint16_t len;
if (is_dtd) {
vdpy_edid_set_frame(&frame, b_param);
vdpy_edid_set_dtd(desc, &frame);
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, 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;
}
}
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;
uint8_t *desc;
base_param b_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, ESTT);
/* edid[53:38], Standard Timings: Identification 1 -> 8 */
vdpy_edid_set_timing(edid, STDT);
/* edid[125:54], Detailed Timing Descriptor - 18 bytes x 4 */
// Preferred Timing Mode
desc = edid + 54;
vdpy_edid_set_descripter(desc, 0x1, 0, &b_param);
// Display Range Limits & Additional Timing Descriptor (tag #FDh)
desc += 18;
vdpy_edid_set_descripter(desc, 0, 0xfd, &b_param);
// Display Product Name (ASCII) String Descriptor (tag #FCh)
desc += 18;
vdpy_edid_set_descripter(desc, 0, 0xfc, &b_param);
// Display Product Serial Number Descriptor (tag #FFh)
desc += 18;
vdpy_edid_set_descripter(desc, 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, int scanout_id, uint8_t *edid, size_t size)
{
struct edid_info edid_info;
struct vscreen *vscr;
if (scanout_id >= vdpy.vscrs_num)
return;
vscr = vdpy.vscrs + scanout_id;
if (handle == vdpy.s.n_connect) {
edid_info.prefx = vscr->info.width;
edid_info.prefy = vscr->info.height;
edid_info.maxx = VDPY_MAX_WIDTH;
edid_info.maxy = VDPY_MAX_HEIGHT;
} else {
edid_info.prefx = VDPY_DEFAULT_WIDTH;
edid_info.prefy = VDPY_DEFAULT_HEIGHT;
edid_info.maxx = VDPY_MAX_WIDTH;
edid_info.maxy = VDPY_MAX_HEIGHT;
}
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, int scanout_id, struct display_info *info)
{
struct vscreen *vscr;
if (scanout_id >= vdpy.vscrs_num)
return;
vscr = vdpy.vscrs + scanout_id;
if (handle == vdpy.s.n_connect) {
info->xoff = vscr->info.xoff;
info->yoff = vscr->info.yoff;
info->width = vscr->info.width;
info->height = vscr->info.height;
} else {
info->xoff = 0;
info->yoff = 0;
info->width = 0;
info->height = 0;
}
}
static void
sdl_gl_display_init(void)
{
struct egl_display_ops *gl_ops = &vdpy.gl_ops;
struct vscreen *vscr;
int i;
/* obtain the eglDisplay/eglContext */
vdpy.eglDisplay = eglGetCurrentDisplay();
vdpy.eglContext = SDL_GL_GetCurrentContext();
/* Try to use the eglGetProcaddress to obtain callback API for
* eglCreateImageKHR/eglDestroyImageKHR
* glEGLImageTargetTexture2DOES
*/
gl_ops->eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)
eglGetProcAddress("eglCreateImageKHR");
gl_ops->eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)
eglGetProcAddress("eglDestroyImageKHR");
gl_ops->glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)
eglGetProcAddress("glEGLImageTargetTexture2DOES");
for (i = 0; i < vdpy.vscrs_num; i++) {
vscr = vdpy.vscrs + i;
vscr->egl_img = EGL_NO_IMAGE_KHR;
}
if ((gl_ops->eglCreateImageKHR == NULL) ||
(gl_ops->eglDestroyImageKHR == NULL) ||
(gl_ops->glEGLImageTargetTexture2DOES == NULL)) {
pr_info("DMABuf is not supported.\n");
vdpy.egl_dmabuf_supported = false;
} else
vdpy.egl_dmabuf_supported = true;
return;
}
static void sdl_gl_prepare_draw(struct vscreen *vscr)
{
SDL_Rect bogus_rect;
if (vscr == NULL)
return;
bogus_rect.x = 0;
bogus_rect.y = 0;
bogus_rect.w = 32;
bogus_rect.h = 32;
/* The limitation in libSDL causes that ACRN can't display framebuffer
* correctly on one window when using multi SDL_context to displaying
* the framebuffers under multi-display scenario.
* The small texture is added to workaround the display issue caused by
* libSDL limitation.
* Todo: Keep monitoring the libSDL to check whether the limitation is
* fixed.
*/
SDL_RenderClear(vscr->renderer);
SDL_RenderCopy(vscr->renderer, vscr->bogus_tex, NULL, &bogus_rect);
return;
}
void
vdpy_surface_set(int handle, int scanout_id, struct surface *surf)
{
pixman_image_t *src_img;
int format;
int access, i;
struct vscreen *vscr;
if (handle != vdpy.s.n_connect) {
return;
}
if (vdpy.tid != pthread_self()) {
pr_err("%s: unexpected code path as unsafe 3D ops in multi-threads env.\n",
__func__);
return;
}
if (scanout_id >= vdpy.vscrs_num) {
return;
}
vscr = vdpy.vscrs + scanout_id;
if (surf == NULL ) {
vscr->surf.width = 0;
vscr->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;
}
vscr->guest_width = VDPY_MIN_WIDTH;
vscr->guest_height = VDPY_MIN_HEIGHT;
} else if (surf->surf_type == SURFACE_PIXMAN) {
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;
}
vscr->surf = *surf;
vscr->guest_width = surf->width;
vscr->guest_height = surf->height;
} else if (surf->surf_type == SURFACE_DMABUF) {
src_img = NULL;
vscr->surf = *surf;
vscr->guest_width = surf->width;
vscr->guest_height = surf->height;
} else {
/* Unsupported type */
return;
}
if (vscr->surf_tex) {
SDL_DestroyTexture(vscr->surf_tex);
}
if (surf && (surf->surf_type == SURFACE_DMABUF)) {
access = SDL_TEXTUREACCESS_STATIC;
format = SDL_PIXELFORMAT_EXTERNAL_OES;
} else {
access = SDL_TEXTUREACCESS_STREAMING;
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));
}
}
vscr->surf_tex = SDL_CreateTexture(vscr->renderer,
format, access,
vscr->guest_width, vscr->guest_height);
if (vscr->surf_tex == 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(vscr->surf_tex, NULL,
pixman_image_get_data(src_img),
pixman_image_get_stride(src_img));
sdl_gl_prepare_draw(vscr);
SDL_RenderCopy(vscr->renderer, vscr->surf_tex, NULL, NULL);
SDL_RenderPresent(vscr->renderer);
} else if (surf->surf_type == SURFACE_DMABUF) {
EGLImageKHR egl_img = EGL_NO_IMAGE_KHR;
EGLint attrs[64];
struct egl_display_ops *gl_ops;
gl_ops = &vdpy.gl_ops;
i = 0;
attrs[i++] = EGL_WIDTH;
attrs[i++] = surf->width;
attrs[i++] = EGL_HEIGHT;
attrs[i++] = surf->height;
attrs[i++] = EGL_LINUX_DRM_FOURCC_EXT;
attrs[i++] = surf->dma_info.surf_fourcc;
attrs[i++] = EGL_DMA_BUF_PLANE0_FD_EXT;
attrs[i++] = surf->dma_info.dmabuf_fd;
attrs[i++] = EGL_DMA_BUF_PLANE0_PITCH_EXT;
attrs[i++] = surf->stride;
attrs[i++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
attrs[i++] = surf->dma_info.dmabuf_offset;
attrs[i++] = EGL_NONE;
egl_img = gl_ops->eglCreateImageKHR(vdpy.eglDisplay,
EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT,
NULL, attrs);
if (egl_img == EGL_NO_IMAGE_KHR) {
pr_err("Failed in eglCreateImageKHR.\n");
return;
}
SDL_GL_BindTexture(vscr->surf_tex, NULL, NULL);
gl_ops->glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, egl_img);
if (vscr->egl_img != EGL_NO_IMAGE_KHR)
gl_ops->eglDestroyImageKHR(vdpy.eglDisplay,
vscr->egl_img);
/* In theory the created egl_img can be released after it is bound
* to texture.
* Now it is released next time so that it is controlled correctly
*/
vscr->egl_img = egl_img;
}
if (vscr->img)
pixman_image_unref(vscr->img);
if (surf == NULL) {
SDL_SetWindowTitle(vscr->win,
"Not activate display yet!");
} else {
SDL_SetWindowTitle(vscr->win,
"ACRN Virtual Monitor");
}
/* Replace the cur_img with the created_img */
vscr->img = src_img;
}
void
vdpy_cursor_position_transformation(struct display *vdpy, int scanout_id, SDL_Rect *rect)
{
struct vscreen *vscr;
if (scanout_id >= vdpy->vscrs_num) {
return;
}
vscr = vdpy->vscrs + scanout_id;
rect->x = (vscr->cur.x * vscr->width) / vscr->guest_width;
rect->y = (vscr->cur.y * vscr->height) / vscr->guest_height;
rect->w = (vscr->cur.width * vscr->width) / vscr->guest_width;
rect->h = (vscr->cur.height * vscr->height) / vscr->guest_height;
}
void
vdpy_surface_update(int handle, int scanout_id, struct surface *surf)
{
SDL_Rect cursor_rect;
struct vscreen *vscr;
if (handle != vdpy.s.n_connect) {
return;
}
if (vdpy.tid != pthread_self()) {
pr_err("%s: unexpected code path as unsafe 3D ops in multi-threads env.\n",
__func__);
return;
}
if (!surf) {
pr_err("Incorrect order of submitting Virtio-GPU cmd.\n");
return;
}
if (scanout_id >= vdpy.vscrs_num) {
return;
}
vscr = vdpy.vscrs + scanout_id;
if (surf->surf_type == SURFACE_PIXMAN)
SDL_UpdateTexture(vscr->surf_tex, NULL,
surf->pixel,
surf->stride);
sdl_gl_prepare_draw(vscr);
SDL_RenderCopy(vscr->renderer, vscr->surf_tex, NULL, NULL);
/* This should be handled after rendering the surface_texture.
* Otherwise it will be hidden
*/
if (vscr->cur_tex) {
vdpy_cursor_position_transformation(&vdpy, scanout_id, &cursor_rect);
SDL_RenderCopy(vscr->renderer, vscr->cur_tex,
NULL, &cursor_rect);
}
SDL_RenderPresent(vscr->renderer);
/* update the rendering time */
clock_gettime(CLOCK_MONOTONIC, &vscr->last_time);
}
void
vdpy_cursor_define(int handle, int scanout_id, struct cursor *cur)
{
struct vscreen *vscr;
if (handle != vdpy.s.n_connect) {
return;
}
if (vdpy.tid != pthread_self()) {
pr_err("%s: unexpected code path as unsafe 3D ops in multi-threads env.\n",
__func__);
return;
}
if (scanout_id >= vdpy.vscrs_num) {
return;
}
if (cur->data == NULL)
return;
vscr = vdpy.vscrs + scanout_id;
if (vscr->cur_tex)
SDL_DestroyTexture(vscr->cur_tex);
vscr->cur_tex = SDL_CreateTexture(
vscr->renderer,
SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING,
cur->width, cur->height);
if (vscr->cur_tex == NULL) {
pr_err("Failed to create sdl_cursor surface for %p.\n", cur);
return;
}
SDL_SetTextureBlendMode(vscr->cur_tex, SDL_BLENDMODE_BLEND);
vscr->cur = *cur;
SDL_UpdateTexture(vscr->cur_tex, NULL, cur->data, cur->width * 4);
}
void
vdpy_cursor_move(int handle, int scanout_id, uint32_t x, uint32_t y)
{
struct vscreen *vscr;
if (handle != vdpy.s.n_connect) {
return;
}
if (scanout_id >= vdpy.vscrs_num) {
return;
}
vscr = vdpy.vscrs + scanout_id;
/* Only move the position of the cursor. The cursor_texture
* will be handled in surface_update
*/
vscr->cur.x = x;
vscr->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;
struct vscreen *vscr;
int i;
ui_vdpy = (struct display *)data;
for (i = 0; i < vdpy.vscrs_num; i++) {
vscr = ui_vdpy->vscrs + i;
/* Skip it if no surface needs to be rendered */
if (vscr->surf_tex == NULL)
continue;
clock_gettime(CLOCK_MONOTONIC, &cur_time);
elapsed_time = (cur_time.tv_sec - vscr->last_time.tv_sec) * 1000000000 +
cur_time.tv_nsec - vscr->last_time.tv_nsec;
/* the time interval is less than 10ms. Skip it */
if (elapsed_time < 10000000)
return;
sdl_gl_prepare_draw(vscr);
SDL_RenderCopy(vscr->renderer, vscr->surf_tex, NULL, NULL);
/* This should be handled after rendering the surface_texture.
* Otherwise it will be hidden
*/
if (vscr->cur_tex) {
vdpy_cursor_position_transformation(ui_vdpy, i, &cursor_rect);
SDL_RenderCopy(vscr->renderer, vscr->cur_tex,
NULL, &cursor_rect);
}
SDL_RenderPresent(vscr->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);
}
void
vdpy_calibrate_vscreen_geometry(struct vscreen *vscr)
{
if (vscr->guest_width && vscr->guest_height) {
/* clip the region between (640x480) and (1920x1080) */
if (vscr->guest_width < VDPY_MIN_WIDTH)
vscr->guest_width = VDPY_MIN_WIDTH;
if (vscr->guest_width > VDPY_MAX_WIDTH)
vscr->guest_width = VDPY_MAX_WIDTH;
if (vscr->guest_height < VDPY_MIN_HEIGHT)
vscr->guest_height = VDPY_MIN_HEIGHT;
if (vscr->guest_height > VDPY_MAX_HEIGHT)
vscr->guest_height = VDPY_MAX_HEIGHT;
} else {
/* the default window(1280x720) is created with undefined pos
* when no geometry info is passed
*/
vscr->org_x = 0xFFFF;
vscr->org_y = 0xFFFF;
vscr->guest_width = VDPY_DEFAULT_WIDTH;
vscr->guest_height = VDPY_DEFAULT_HEIGHT;
}
}
int
vdpy_create_vscreen_window(struct vscreen *vscr)
{
uint32_t win_flags;
win_flags = SDL_WINDOW_OPENGL |
SDL_WINDOW_ALWAYS_ON_TOP |
SDL_WINDOW_SHOWN;
if (vscr->is_fullscreen) {
win_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
vscr->org_x = vscr->pscreen_rect.x;
vscr->org_y = vscr->pscreen_rect.y;
vscr->width = vscr->pscreen_rect.w;
vscr->height = vscr->pscreen_rect.h;
} else {
vscr->width = vscr->guest_width;
vscr->height = vscr->guest_height;
}
vscr->win = NULL;
vscr->renderer = NULL;
vscr->img = NULL;
// Zoom to width and height of pscreen is fullscreen enabled
vscr->win = SDL_CreateWindow("ACRN_DM",
vscr->org_x, vscr->org_y,
vscr->width, vscr->height,
win_flags);
if (vscr->win == NULL) {
pr_err("Failed to Create SDL_Window\n");
return -1;
}
pr_info("SDL display bind to screen %d: [%d,%d,%d,%d].\n", vscr->pscreen_id,
vscr->org_x, vscr->org_y, vscr->width, vscr->height);
vscr->renderer = SDL_CreateRenderer(vscr->win, -1, 0);
if (vscr->renderer == NULL) {
pr_err("Failed to Create GL_Renderer \n");
return -1;
}
vscr->bogus_tex = SDL_CreateTexture(vscr->renderer,
SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC,
32, 32);
if (vscr->bogus_tex == NULL) {
pr_err("%s: Failed to create SDL_Texture\n", __func__);
return -1;
}
SDL_SetTextureColorMod(vscr->bogus_tex, 0x80, 0x80, 0x80);
return 0;
}
static void *
vdpy_sdl_display_thread(void *data)
{
struct vdpy_display_bh *bh;
struct itimerspec ui_timer_spec;
struct vscreen *vscr;
int i;
for (i = 0; i < vdpy.vscrs_num; i++) {
vscr = vdpy.vscrs + i;
vdpy_calibrate_vscreen_geometry(vscr);
vscr->info.xoff = vscr->org_x;
vscr->info.yoff = vscr->org_y;
vscr->info.width = vscr->guest_width;
vscr->info.height = vscr->guest_height;
if (vdpy_create_vscreen_window(vscr)) {
goto sdl_fail;
}
clock_gettime(CLOCK_MONOTONIC, &vscr->last_time);
}
sdl_gl_display_init();
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;
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);
for (i = 0; i < vdpy.vscrs_num; i++) {
vscr = vdpy.vscrs + i;
if (vscr->img) {
pixman_image_unref(vscr->img);
vscr->img = NULL;
}
/* Continue to thread cleanup */
if (vscr->surf_tex) {
SDL_DestroyTexture(vscr->surf_tex);
vscr->surf_tex = NULL;
}
if (vscr->cur_tex) {
SDL_DestroyTexture(vscr->cur_tex);
vscr->cur_tex = NULL;
}
if (vdpy.egl_dmabuf_supported && (vscr->egl_img != EGL_NO_IMAGE_KHR))
vdpy.gl_ops.eglDestroyImageKHR(vdpy.eglDisplay,
vscr->egl_img);
}
sdl_fail:
for (i = 0; i < vdpy.vscrs_num; i++) {
vscr = vdpy.vscrs + i;
if (vscr->bogus_tex) {
SDL_DestroyTexture(vscr->bogus_tex);
vscr->bogus_tex = NULL;
}
if (vscr->renderer) {
SDL_DestroyRenderer(vscr->renderer);
vscr->renderer = NULL;
}
if (vscr->win) {
SDL_DestroyWindow(vscr->win);
vscr->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 *num_vscreens)
{
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;
}
pthread_setname_np(vdpy.tid, "acrn_vdisplay");
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++;
if (num_vscreens)
*num_vscreens = vdpy.vscrs_num;
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;
}
int
gfx_ui_init()
{
SDL_SysWMinfo info;
int num_pscreen;
struct vscreen *vscr;
int i;
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\n");
return -1;
}
if (vdpy.vscrs_num <= 0) {
pr_err("Incorrect geometry parameter for virtio-gpu\n");
return -1;
}
num_pscreen = SDL_GetNumVideoDisplays();
for (i = 0; i < vdpy.vscrs_num; i++) {
vscr = vdpy.vscrs + i;
if (vscr->pscreen_id >= num_pscreen) {
pr_err("Monitor id %d is out of avalble range [0~%d].\n",
vscr->pscreen_id, num_pscreen);
SDL_Quit();
return -1;
}
SDL_GetDisplayBounds(vscr->pscreen_id, &vscr->pscreen_rect);
if (vscr->pscreen_rect.w < VDPY_MIN_WIDTH ||
vscr->pscreen_rect.h < VDPY_MIN_HEIGHT) {
pr_err("Too small resolutions. Please check the "
" graphics system\n");
SDL_Quit();
return -1;
}
}
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;
return 0;
}
void
gfx_ui_deinit()
{
if (!vdpy.s.is_ui_realized) {
return;
}
free(vdpy.vscrs);
SDL_Quit();
pr_info("SDL_Quit\r\n");
}
int vdpy_parse_cmd_option(const char *opts)
{
char *str, *stropts, *tmp;
int snum, error;
struct vscreen *vscr;
error = 0;
vdpy.vscrs = calloc(VSCREEN_MAX_NUM, sizeof(struct vscreen));
vdpy.vscrs_num = 0;
stropts = strdup(opts);
while ((str = strsep(&stropts, ",")) != NULL) {
vscr = vdpy.vscrs + vdpy.vscrs_num;
tmp = strcasestr(str, "geometry=");
if (str && strcasestr(str, "geometry=fullscreen")) {
snum = sscanf(tmp, "geometry=fullscreen:%d", &vscr->pscreen_id);
if (snum != 1) {
vscr->pscreen_id = 0;
}
vscr->org_x = 0;
vscr->org_y = 0;
vscr->guest_width = VDPY_MAX_WIDTH;
vscr->guest_height = VDPY_MAX_HEIGHT;
vscr->is_fullscreen = true;
pr_info("virtual display: fullscreen on monitor %d.\n",
vscr->pscreen_id);
vdpy.vscrs_num++;
} else if (str && strcasestr(str, "geometry=")) {
snum = sscanf(tmp, "geometry=%dx%d+%d+%d",
&vscr->guest_width, &vscr->guest_height,
&vscr->org_x, &vscr->org_y);
if (snum != 4) {
pr_err("incorrect geometry option. Should be"
" WxH+x+y\n");
error = -1;
}
vscr->is_fullscreen = false;
vscr->pscreen_id = 0;
pr_info("virtual display: windowed on monitor %d.\n",
vscr->pscreen_id);
vdpy.vscrs_num++;
}
if (vdpy.vscrs_num > VSCREEN_MAX_NUM) {
pr_err("%d virtual displays are too many that acrn-dm can't support!\n");
break;
}
}
free(stropts);
return error;
}