/* * 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" #include #include #include #include #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; }