ui: Move QEMU main loop to a thread

This commit is contained in:
Matt Borgerson 2020-03-19 17:45:44 -07:00
parent 03e1d6993c
commit c2b74a08d3
3 changed files with 187 additions and 35 deletions

View File

@ -36,11 +36,18 @@ struct _GloContext {
SDL_GLContext gl_context;
};
static GloContext *g_context;
/* Create an OpenGL context */
GloContext *glo_context_create(void)
{
fprintf(stderr, "%s\n", __func__);
if (g_context) {
glo_set_current(g_context);
return g_context;
}
GloContext *context = (GloContext *)malloc(sizeof(GloContext));
assert(context != NULL);
@ -82,6 +89,8 @@ GloContext *glo_context_create(void)
glo_set_current(context);
g_context = context;
return context;
}

View File

@ -2967,6 +2967,12 @@ void qemu_init(int argc, char **argv, char **envp)
fake_argv[fake_argc++] = strdup("-display");
fake_argv[fake_argc++] = strdup("xemu");
#ifdef _WIN32
// FIXME: Create this dummy device to prevent logspam
fake_argv[fake_argc++] = strdup("-audiodev");
fake_argv[fake_argc++] = strdup("none,id=snd0");
#endif
printf("Created QEMU launch parameters: ");
for (int i = 0; i < fake_argc; i++) {
printf("%s ", fake_argv[i]);

207
ui/xemu.c
View File

@ -29,6 +29,8 @@
#include "qemu/osdep.h"
#include "qemu/module.h"
#include "qemu/thread.h"
#include "qemu/main-loop.h"
#include "qemu-version.h"
#include "ui/console.h"
#include "ui/input.h"
@ -39,21 +41,28 @@
#include "xemu-input.h"
#include "xemu-settings.h"
#include "xemu-shaders.h"
#include "hw/xbox/nv2a/gl/gloffscreen.h" // FIXME
// #define DEBUG_XEMU_C
#ifdef DEBUG_XEMU_C
#define DPRINTF(...) fprintf(stderr, __VA_ARGS__)
#else
#define DPRINTF(...)
#endif
void xb_surface_gl_create_texture(DisplaySurface *surface);
void xb_surface_gl_update_texture(DisplaySurface *surface, int x, int y, int w, int h);
void xb_surface_gl_destroy_texture(DisplaySurface *surface);
// FIXME: Move this to a better location
void pre_swap(void);
void post_swap(void);
static void pre_swap(void);
static void post_swap(void);
static void sleep_ns(int64_t ns);
static int sdl2_num_outputs;
static struct sdl2_console *sdl2_console;
static SDL_Surface *guest_sprite_surface;
static int gui_grab; /* if true, all keyboard/mouse events are grabbed */
static int gui_saved_grab;
static int gui_fullscreen;
static int gui_grab_code = KMOD_LALT | KMOD_LCTRL;
@ -64,10 +73,13 @@ static int guest_cursor;
static int guest_x, guest_y;
static SDL_Cursor *guest_sprite;
static Notifier mouse_mode_notifier;
static SDL_Window *m_window;
static SDL_GLContext m_context;
int scaling_mode = 1;
struct decal_shader *blit;
static QemuSemaphore display_init_sem;
static void toggle_full_screen(struct sdl2_console *scon);
int xemu_is_fullscreen(void)
@ -716,6 +728,7 @@ static void sdl_mouse_define(DisplayChangeListener *dcl,
}
}
#if 0
static void sdl_cleanup(void)
{
if (guest_sprite) {
@ -723,13 +736,14 @@ static void sdl_cleanup(void)
}
SDL_QuitSubSystem(SDL_INIT_VIDEO);
}
#endif
static const DisplayChangeListenerOps dcl_gl_ops = {
.dpy_name = "sdl2-gl",
.dpy_gfx_update = sdl2_gl_update,
.dpy_gfx_switch = sdl2_gl_switch,
.dpy_gfx_check_format = console_gl_check_format,
.dpy_refresh = sdl2_gl_refresh,
// .dpy_refresh = sdl2_gl_refresh,
.dpy_mouse_set = sdl_mouse_warp,
.dpy_cursor_define = sdl_mouse_define,
@ -742,14 +756,8 @@ static const DisplayChangeListenerOps dcl_gl_ops = {
.dpy_gl_update = sdl2_gl_scanout_flush,
};
SDL_Window *m_window;
SDL_GLContext m_context;
static void sdl2_display_early_init(DisplayOptions *o)
static void sdl2_display_very_early_init(DisplayOptions *o)
{
assert(o->type == DISPLAY_TYPE_XEMU);
display_opengl = 1;
fprintf(stderr, "%s\n", __func__);
#ifdef __linux__
@ -770,6 +778,7 @@ static void sdl2_display_early_init(DisplayOptions *o)
SDL_GetError());
exit(1);
}
#ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR /* only available since SDL 2.0.8 */
SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
#endif
@ -812,6 +821,31 @@ static void sdl2_display_early_init(DisplayOptions *o)
exit(1);
}
int width, height, channels = 0;
stbi_set_flip_vertically_on_load(0);
unsigned char *icon_data = stbi_load("./data/xemu_64x64.png", &width, &height, &channels, 4);
if (icon_data) {
SDL_Surface *icon = SDL_CreateRGBSurfaceFrom(icon_data, width, height, 32, width*4,
0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
if (icon) {
SDL_SetWindowIcon(m_window, icon);
}
// Note: Retaining the memory allocated by stbi_load. It's used in place
// by the SDL surface.
}
// Initialize offscreen rendering context now
glo_context_create();
SDL_GL_MakeCurrent(NULL, NULL);
// FIXME: atexit(sdl_cleanup);
}
static void sdl2_display_early_init(DisplayOptions *o)
{
assert(o->type == DISPLAY_TYPE_XEMU);
display_opengl = 1;
SDL_GL_MakeCurrent(m_window, m_context);
xemu_hud_init(m_window, m_context);
blit = create_decal_shader(SHADER_TYPE_BLIT);
@ -822,9 +856,9 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
uint8_t data = 0;
int i;
SDL_SysWMinfo info;
SDL_Surface *icon = NULL;
assert(o->type == DISPLAY_TYPE_XEMU);
SDL_GL_MakeCurrent(m_window, m_context);
xemu_input_init();
xemu_settings_get_enum(XEMU_SETTINGS_DISPLAY_SCALE, &scaling_mode);
@ -879,23 +913,6 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
#endif
}
int width, height, channels = 0;
stbi_set_flip_vertically_on_load(0);
unsigned char *icon_data = stbi_load("./data/xemu_64x64.png", &width, &height, &channels, 4);
if (icon_data) {
icon = SDL_CreateRGBSurfaceFrom(icon_data, width, height, 32, width*4,
0x000000ff,
0x0000ff00,
0x00ff0000,
0xff000000
);
// Note: Retaining the memory allocated by stbi_load. It's used in-place
// by the SDL surface.
}
if (icon) {
SDL_SetWindowIcon(sdl2_console[0].real_window, icon);
}
gui_grab = 0;
if (gui_fullscreen) {
sdl_grab_start(0);
@ -907,7 +924,9 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
sdl_cursor_hidden = SDL_CreateCursor(&data, &data, 8, 1, 0, 0);
sdl_cursor_normal = SDL_GetCursor();
atexit(sdl_cleanup);
/* Tell main thread to go ahead and create the app and enter the run loop */
SDL_GL_MakeCurrent(NULL, NULL);
qemu_sem_post(&display_init_sem);
}
static QemuDisplay qemu_display_sdl2 = {
@ -1062,7 +1081,10 @@ static void xemu_sdl2_gl_render_surface(struct sdl2_console *scon)
glClear(GL_COLOR_BUFFER_BIT);
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL);
// FIXME: Finer locking
qemu_mutex_lock_iothread();
xemu_hud_render(scon->real_window);
qemu_mutex_unlock_iothread();
// xb_surface_gl_render_texture(scon->surface);
pre_swap();
@ -1123,17 +1145,39 @@ void sdl2_gl_switch(DisplayChangeListener *dcl,
xb_surface_gl_create_texture(scon->surface);
}
float fps = 1.0;
static void update_fps(void)
{
static int64_t last_update = 0;
const float r = 0.5;//0.1;
static float avg = 1.0;
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
float ms = ((float)(now-last_update)/1000000.0);
last_update = now;
if (fabs(avg-ms) > 0.25*avg) avg = ms;
else avg = avg*(1.0-r)+ms*r;
fps = 1000.0/avg;
}
void sdl2_gl_refresh(DisplayChangeListener *dcl)
{
struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
assert(scon->opengl);
update_fps();
SDL_GL_MakeCurrent(scon->real_window, scon->winctx);
qemu_mutex_lock_iothread();
graphic_hw_update(dcl->con);
if (scon->updates && scon->surface) {
scon->updates = 0;
}
xemu_sdl2_gl_render_surface(scon);
sdl2_poll_events(scon);
qemu_mutex_unlock_iothread();
xemu_sdl2_gl_render_surface(scon);
}
void sdl2_gl_redraw(struct sdl2_console *scon)
@ -1147,7 +1191,7 @@ void sdl2_gl_redraw(struct sdl2_console *scon)
// return sdl2_gl_scanout_flush(&scon->dcl, 0, 0, 0, 0);
}
if (scon->surface) {
xemu_sdl2_gl_render_surface(scon);
// xemu_sdl2_gl_render_surface(scon);
}
}
@ -1307,3 +1351,96 @@ void sdl2_process_key(struct sdl2_console *scon,
}
}
}
int gArgc;
char **gArgv;
// vl.c
int qemu_main(int argc, char **argv, char **envp);
static void *call_qemu_main(void *opaque)
{
int status;
DPRINTF("Second thread: calling qemu_main()\n");
status = qemu_main(gArgc, gArgv, NULL);
DPRINTF("Second thread: qemu_main() returned, exiting\n");
exit(status);
}
static void pre_swap(void)
{
}
/* Note: only supports millisecond resolution on Windows */
static void sleep_ns(int64_t ns)
{
#ifndef _WIN32
struct timespec sleep_delay, rem_delay;
sleep_delay.tv_sec = ns / 1000000000LL;
sleep_delay.tv_nsec = ns % 1000000000LL;
nanosleep(&sleep_delay, &rem_delay);
#else
Sleep(ns / SCALE_MS);
#endif
}
static void post_swap(void)
{
// Throttle to make sure swaps happen at 60Hz
static int64_t last_update = 0;
int64_t deadline = last_update + 16666666;
int64_t sleep_acc = 0;
int64_t spin_acc = 0;
#ifndef _WIN32
const int64_t sleep_threshold = 2000000;
#else
const int64_t sleep_threshold = 250000;
#endif
while (1) {
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
int64_t time_remaining = deadline - now;
if (now < deadline) {
if (time_remaining > sleep_threshold) {
// Try to sleep until the until reaching the sleep threshold.
sleep_ns(time_remaining - sleep_threshold);
sleep_acc += qemu_clock_get_ns(QEMU_CLOCK_REALTIME)-now;
} else {
// Simply spin to avoid extra delays incurred with swapping to
// another process and back in the event of being within
// threshold to desired event.
spin_acc++;
}
} else {
DPRINTF("zzZz %g %ld\n", (double)sleep_acc/1000000.0, spin_acc);
last_update = now;
break;
}
}
}
// #undef main
int main(int argc, char **argv) {
QemuThread thread;
DPRINTF("Entered main()\n");
gArgc = argc;
gArgv = argv;
sdl2_display_very_early_init(NULL);
qemu_sem_init(&display_init_sem, 0);
qemu_thread_create(&thread, "qemu_main", call_qemu_main,
NULL, QEMU_THREAD_DETACHED);
DPRINTF("Main thread: waiting for display_init_sem\n");
qemu_sem_wait(&display_init_sem);
DPRINTF("Main thread: initializing app\n");
while (1) {
sdl2_gl_refresh(&sdl2_console[0].dcl);
}
}