From 3b7a83d64b2f3f7ceed98aa19e40593b39d8e9d8 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sat, 31 Jan 2026 13:16:21 -0700 Subject: [PATCH] ui: Handle main window refresh independently - Cleanup various dead code and minor restructure for clarity - Separate host and guest updates - Fix freezes while moving or resizing the main window on Windows by registering event watch callback --- include/ui/xemu-display.h | 32 -- ui/sdl2.c | 8 +- ui/xemu.c | 829 ++++++++++++++++++++------------------ ui/xui/main.cc | 5 +- ui/xui/misc.cc | 2 + ui/xui/xemu-hud.h | 3 + 6 files changed, 448 insertions(+), 431 deletions(-) delete mode 100644 include/ui/xemu-display.h diff --git a/include/ui/xemu-display.h b/include/ui/xemu-display.h deleted file mode 100644 index 2e35582f8e..0000000000 --- a/include/ui/xemu-display.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef XEMU_DISPLAY_H -#define XEMU_DISPLAY_H - -/* Avoid compiler warning because macro is redefined in SDL_syswm.h. */ -#undef WIN32_LEAN_AND_MEAN - -#include - -#include "ui/kbd-state.h" - -struct xemu_console { - DisplayChangeListener dcl; - DisplaySurface *surface; - DisplayOptions *opts; - SDL_Texture *texture; - SDL_Window *real_window; - SDL_Renderer *real_renderer; - int idx; - int last_vm_running; /* per console for caption reasons */ - int x, y, w, h; - int hidden; - int opengl; - int updates; - int idle_counter; - int ignore_hotkeys; - SDL_GLContext winctx; - QKbdState *kbd; - bool y0_top; - bool scanout_mode; -}; - -#endif /* XEMU_DISPLAY_H */ diff --git a/ui/sdl2.c b/ui/sdl2.c index 032dc14bc3..2b8994cb8f 100644 --- a/ui/sdl2.c +++ b/ui/sdl2.c @@ -285,7 +285,7 @@ static void absolute_mouse_grab(struct sdl2_console *scon) } } -static void sdl_mouse_mode_change(Notifier *notify, void *data) +static void mouse_mode_change(Notifier *notify, void *data) { if (qemu_input_is_absolute(sdl2_console[0].dcl.con)) { if (!absolute_enabled) { @@ -939,7 +939,7 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o) SDL_SetWindowIcon(sdl2_console[0].real_window, icon); } - mouse_mode_notifier.notify = sdl_mouse_mode_change; + mouse_mode_notifier.notify = mouse_mode_change; qemu_add_mouse_mode_change_notifier(&mouse_mode_notifier); sdl_cursor_hidden = SDL_CreateCursor(&data, &data, 8, 1, 0, 0); @@ -961,12 +961,12 @@ static QemuDisplay qemu_display_sdl2 = { .init = sdl2_display_init, }; -static void register_sdl1(void) +static void register_xemu_display(void) { qemu_display_register(&qemu_display_sdl2); } -type_init(register_sdl1); +type_init(register_xemu_display); #ifdef CONFIG_OPENGL module_dep("ui-opengl"); diff --git a/ui/xemu.c b/ui/xemu.c index ba04400a51..c2a544e138 100644 --- a/ui/xemu.c +++ b/ui/xemu.c @@ -39,14 +39,13 @@ #include "qobject/qdict.h" #include "ui/console.h" #include "ui/input.h" -#include "ui/xemu-display.h" +#include "ui/kbd-state.h" #include "system/runstate.h" #include "system/runstate-action.h" #include "system/system.h" #include "xui/xemu-hud.h" #include "xemu-input.h" #include "xemu-settings.h" -// #include "xemu-shaders.h" #include "xemu-snapshots.h" #include "xemu-version.h" #include "xemu-os-utils.h" @@ -60,6 +59,32 @@ #include #include #include +#include + +#ifndef DEBUG_XEMU_C +#define DEBUG_XEMU_C 0 +#endif + +#if DEBUG_XEMU_C +#define DPRINTF(...) fprintf(stderr, __VA_ARGS__) +#else +#define DPRINTF(...) +#endif + +uint64_t vblank_interval_ns = 16666666LL; +bool use_vblank_timer_thread = true; + +struct xemu_console { + DisplayChangeListener dcl; + DisplaySurface *surface; + DisplayOptions *opts; + SDL_Window *real_window; + int idx; + int hidden; + int ignore_hotkeys; + SDL_GLContext winctx; + QKbdState *kbd; +}; #ifdef _WIN32 #include "nvapi.h" @@ -70,38 +95,6 @@ __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1; __declspec(dllexport) DWORD NvOptimusEnablement = 1; #endif -void tcg_register_init_ctx(void); // tcg.c - -// #define DEBUG_XEMU_C - -#ifdef DEBUG_XEMU_C -#define DPRINTF(...) fprintf(stderr, __VA_ARGS__) -#else -#define DPRINTF(...) -#endif - -static bool xb_console_gl_check_format(DisplayChangeListener *dcl, - pixman_format_code_t format) -{ - switch (format) { - case PIXMAN_BE_b8g8r8x8: - case PIXMAN_BE_b8g8r8a8: - case PIXMAN_r5g6b5: - return true; - default: - return false; - } -} - -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); - -static void process_key(struct xemu_console *scon, SDL_KeyboardEvent *ev); -static void gl_update(DisplayChangeListener *dcl, int x, int y, int w, int h); -static void gl_switch(DisplayChangeListener *dcl, DisplaySurface *new_surface); -static void gl_redraw(struct xemu_console *scon); - static int num_outputs; static struct xemu_console *scon_list; static SDL_Surface *guest_sprite_surface; @@ -120,20 +113,32 @@ static SDL_Cursor *guest_sprite; static Notifier mouse_mode_notifier; static SDL_Window *m_window; static SDL_GLContext m_context; -// struct decal_shader *blit; - static QemuSemaphore display_init_sem; +static QEMUTimer *vblank_timer; -static void toggle_full_screen(struct xemu_console *scon); +void tcg_register_init_ctx(void); // tcg.c -int xemu_is_fullscreen(void) +#if DEBUG_XEMU_C +static uint64_t lock_held_acc; +static uint64_t lock_start; +#endif + +void xemu_main_loop_lock(void) { - return gui_fullscreen; + qemu_mutex_lock_main_loop(); + bql_lock(); +#if DEBUG_XEMU_C + lock_start = qemu_clock_get_ns(QEMU_CLOCK_REALTIME); +#endif } -void xemu_toggle_fullscreen(void) +void xemu_main_loop_unlock(void) { - toggle_full_screen(&scon_list[0]); +#if DEBUG_XEMU_C + lock_held_acc += qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - lock_start; +#endif + bql_unlock(); + qemu_mutex_unlock_main_loop(); } SDL_Window *xemu_get_window(void) @@ -163,18 +168,7 @@ static void window_resize(struct xemu_console *scon) surface_height(scon->surface)); } -static void redraw(struct xemu_console *scon) -{ - if (scon->opengl) { - gl_redraw(scon); - } -} - -static void sdl_update_caption(struct xemu_console *scon) -{ -} - -static void sdl_hide_cursor(struct xemu_console *scon) +static void hide_cursor(struct xemu_console *scon) { if (scon->opts->has_show_cursor && scon->opts->show_cursor) { return; @@ -188,7 +182,7 @@ static void sdl_hide_cursor(struct xemu_console *scon) } } -static void sdl_show_cursor(struct xemu_console *scon) +static void show_cursor(struct xemu_console *scon) { if (scon->opts->has_show_cursor && scon->opts->show_cursor) { return; @@ -208,17 +202,16 @@ static void sdl_show_cursor(struct xemu_console *scon) SDL_ShowCursor(); } -static void sdl_grab_start(struct xemu_console *scon) +static void grab_start(struct xemu_console *scon) { } -static void sdl_grab_end(struct xemu_console *scon) +static void grab_end(struct xemu_console *scon) { SDL_SetWindowKeyboardGrab(scon->real_window, false); SDL_SetWindowMouseGrab(scon->real_window, false); gui_grab = 0; - sdl_show_cursor(scon); - sdl_update_caption(scon); + show_cursor(scon); } static void absolute_mouse_grab(struct xemu_console *scon) @@ -229,11 +222,11 @@ static void absolute_mouse_grab(struct xemu_console *scon) SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h); if (mouse_x > 0 && mouse_x < scr_w - 1 && mouse_y > 0 && mouse_y < scr_h - 1) { - sdl_grab_start(scon); + grab_start(scon); } } -static void sdl_mouse_mode_change(Notifier *notify, void *data) +static void mouse_mode_change(Notifier *notify, void *data) { if (qemu_input_is_absolute(scon_list[0].dcl.con)) { if (!absolute_enabled) { @@ -243,13 +236,13 @@ static void sdl_mouse_mode_change(Notifier *notify, void *data) } } else if (absolute_enabled) { if (!gui_fullscreen) { - sdl_grab_end(&scon_list[0]); + grab_end(&scon_list[0]); } absolute_enabled = 0; } } -static void sdl_send_mouse_event(struct xemu_console *scon, int dx, int dy, +static void send_mouse_event(struct xemu_console *scon, int dx, int dy, int x, int y, int state) { static uint32_t bmap[INPUT_BUTTON__MAX] = { @@ -311,10 +304,10 @@ static void set_full_screen(struct xemu_console *scon, bool set) SDL_free(modes); SDL_SetWindowFullscreen(scon->real_window, true); gui_saved_grab = gui_grab; - sdl_grab_start(scon); + grab_start(scon); } else { if (!gui_saved_grab) { - sdl_grab_end(scon); + grab_end(scon); } SDL_SetWindowFullscreen(scon->real_window, false); } @@ -325,6 +318,16 @@ static void toggle_full_screen(struct xemu_console *scon) set_full_screen(scon, !gui_fullscreen); } +void xemu_toggle_fullscreen(void) +{ + toggle_full_screen(&scon_list[0]); +} + +int xemu_is_fullscreen(void) +{ + return gui_fullscreen; +} + static int get_mod_state(void) { SDL_Keymod mod = SDL_GetModState(); @@ -339,11 +342,22 @@ static int get_mod_state(void) } } +static void process_key(struct xemu_console *scon, SDL_KeyboardEvent *ev) +{ + int qcode; + + if (ev->scancode >= qemu_input_map_usb_to_qcode_len) { + return; + } + qcode = qemu_input_map_usb_to_qcode[ev->scancode]; + qkbd_state_key_event(scon->kbd, qcode, ev->type == SDL_EVENT_KEY_DOWN); +} + static void handle_keydown(SDL_Event *ev) { int win; struct xemu_console *scon = get_scon_from_window(ev->key.windowID); - if (scon == NULL) return; + if (scon == NULL) return; int gui_key_modifier_pressed = get_mod_state(); int gui_keysym = 0; @@ -358,7 +372,7 @@ static void handle_keydown(SDL_Event *ev) case SDL_SCANCODE_8: case SDL_SCANCODE_9: if (gui_grab) { - sdl_grab_end(scon); + grab_end(scon); } win = ev->key.scancode - SDL_SCANCODE_1; @@ -381,9 +395,9 @@ static void handle_keydown(SDL_Event *ev) case SDL_SCANCODE_G: gui_keysym = 1; if (!gui_grab) { - sdl_grab_start(scon); + grab_start(scon); } else if (!gui_fullscreen) { - sdl_grab_end(scon); + grab_end(scon); } break; case SDL_SCANCODE_U: @@ -408,10 +422,6 @@ static void handle_keyup(SDL_Event *ev) process_key(scon, &ev->key); } -static void handle_textinput(SDL_Event *ev) -{ -} - static void handle_mousemotion(SDL_Event *ev) { int max_x, max_y; @@ -429,16 +439,16 @@ static void handle_mousemotion(SDL_Event *ev) if (gui_grab && !gui_fullscreen && (ev->motion.x == 0 || ev->motion.y == 0 || ev->motion.x == max_x || ev->motion.y == max_y)) { - sdl_grab_end(scon); + grab_end(scon); } if (!gui_grab && (ev->motion.x > 0 && ev->motion.x < max_x && ev->motion.y > 0 && ev->motion.y < max_y)) { - sdl_grab_start(scon); + grab_start(scon); } } if (gui_grab || qemu_input_is_absolute(scon->dcl.con) || absolute_enabled) { - sdl_send_mouse_event(scon, ev->motion.xrel, ev->motion.yrel, + send_mouse_event(scon, ev->motion.xrel, ev->motion.yrel, ev->motion.x, ev->motion.y, ev->motion.state); } } @@ -457,7 +467,7 @@ static void handle_mousebutton(SDL_Event *ev) if (!gui_grab && !qemu_input_is_absolute(scon->dcl.con)) { if (ev->type == SDL_EVENT_MOUSE_BUTTON_UP && bev->button == SDL_BUTTON_LEFT) { /* start grabbing all events */ - sdl_grab_start(scon); + grab_start(scon); } } else { if (ev->type == SDL_EVENT_MOUSE_BUTTON_DOWN) { @@ -465,7 +475,7 @@ static void handle_mousebutton(SDL_Event *ev) } else { buttonstate &= ~SDL_BUTTON_MASK(bev->button); } - sdl_send_mouse_event(scon, 0, 0, bev->x, bev->y, buttonstate); + send_mouse_event(scon, 0, 0, bev->x, bev->y, buttonstate); } } @@ -516,10 +526,6 @@ static void handle_windowevent(SDL_Event *ev) g_config.display.window.last_height = ev->window.data2; } } - redraw(scon); - break; - case SDL_EVENT_WINDOW_EXPOSED: - redraw(scon); break; case SDL_EVENT_WINDOW_FOCUS_GAINED: case SDL_EVENT_WINDOW_MOUSE_ENTER: @@ -537,13 +543,9 @@ static void handle_windowevent(SDL_Event *ev) break; case SDL_EVENT_WINDOW_FOCUS_LOST: if (gui_grab && !gui_fullscreen) { - sdl_grab_end(scon); + grab_end(scon); } break; - case SDL_EVENT_WINDOW_RESTORED: - break; - case SDL_EVENT_WINDOW_MINIMIZED: - break; case SDL_EVENT_WINDOW_CLOSE_REQUESTED: if (qemu_console_is_graphic(scon->dcl.con)) { if (scon->opts->has_window_close && !scon->opts->window_close) { @@ -567,76 +569,8 @@ static void handle_windowevent(SDL_Event *ev) } } -static void poll_events(struct xemu_console *scon) -{ - SDL_Event ev1, *ev = &ev1; - bool allow_close = true; - - if (scon->last_vm_running != runstate_is_running()) { - scon->last_vm_running = runstate_is_running(); - sdl_update_caption(scon); - } - - int kbd = 0, mouse = 0; - xemu_hud_should_capture_kbd_mouse(&kbd, &mouse); - - while (SDL_PollEvent(ev)) { - // HUD must process events first so that if a controller is detached, - // a latent rebind request can cancel before the state is freed - xemu_hud_process_sdl_events(ev); - xemu_input_process_sdl_events(ev); - - switch (ev->type) { - case SDL_EVENT_KEY_DOWN: - if (kbd) break; - handle_keydown(ev); - break; - case SDL_EVENT_KEY_UP: - if (kbd) break; - handle_keyup(ev); - break; - case SDL_EVENT_TEXT_INPUT: - if (kbd) break; - handle_textinput(ev); - break; - case SDL_EVENT_QUIT: - if (scon->opts->has_window_close && !scon->opts->window_close) { - allow_close = false; - } - if (allow_close) { - shutdown_action = SHUTDOWN_ACTION_POWEROFF; - qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI); - } - break; - case SDL_EVENT_MOUSE_MOTION: - if (mouse) break; - handle_mousemotion(ev); - break; - case SDL_EVENT_MOUSE_BUTTON_DOWN: - case SDL_EVENT_MOUSE_BUTTON_UP: - if (mouse) break; - handle_mousebutton(ev); - break; - case SDL_EVENT_MOUSE_WHEEL: - if (mouse) break; - handle_mousewheel(ev); - break; - case SDL_EVENT_WINDOW_FIRST ... SDL_EVENT_WINDOW_LAST: - handle_windowevent(ev); - break; - default: - break; - } - } - - xemu_input_update_controllers(); - - scon->idle_counter = 0; - scon->dcl.update_interval = 16; // Ignored -} - -static void sdl_mouse_warp(DisplayChangeListener *dcl, - int x, int y, bool on) +static void mouse_warp(DisplayChangeListener *dcl, + int x, int y, bool on) { struct xemu_console *scon = container_of(dcl, struct xemu_console, dcl); @@ -646,7 +580,7 @@ static void sdl_mouse_warp(DisplayChangeListener *dcl, if (on) { if (!guest_cursor) { - sdl_show_cursor(scon); + show_cursor(scon); } if (gui_grab || qemu_input_is_absolute(scon->dcl.con) || absolute_enabled) { SDL_SetCursor(guest_sprite); @@ -655,13 +589,13 @@ static void sdl_mouse_warp(DisplayChangeListener *dcl, } } } else if (gui_grab) { - sdl_hide_cursor(scon); + hide_cursor(scon); } guest_cursor = on; guest_x = x, guest_y = y; } -static void sdl_mouse_define(DisplayChangeListener *dcl, +static void mouse_define(DisplayChangeListener *dcl, QEMUCursor *c) { @@ -692,14 +626,318 @@ static void sdl_mouse_define(DisplayChangeListener *dcl, } } -static const DisplayChangeListenerOps dcl_gl_ops = { - .dpy_name = "xemu-gl", - .dpy_gfx_update = gl_update, - .dpy_gfx_switch = gl_switch, - .dpy_gfx_check_format = xb_console_gl_check_format, - .dpy_mouse_set = sdl_mouse_warp, - .dpy_cursor_define = sdl_mouse_define, -}; +static void xb_surface_gl_create_texture(DisplaySurface *surface) +{ + assert(QEMU_IS_ALIGNED(surface_stride(surface), surface_bytes_per_pixel(surface))); + + switch (surface_format(surface)) { + case PIXMAN_BE_b8g8r8x8: + case PIXMAN_BE_b8g8r8a8: + surface->glformat = GL_BGRA_EXT; + surface->gltype = GL_UNSIGNED_BYTE; + break; + case PIXMAN_BE_x8r8g8b8: + case PIXMAN_BE_a8r8g8b8: + surface->glformat = GL_RGBA; + surface->gltype = GL_UNSIGNED_BYTE; + break; + case PIXMAN_r5g6b5: + surface->glformat = GL_RGB; + surface->gltype = GL_UNSIGNED_SHORT_5_6_5; + break; + default: + g_assert_not_reached(); + } + + if (!surface->texture) { + glGenTextures(1, &surface->texture); + } + glBindTexture(GL_TEXTURE_2D, surface->texture); + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, + surface_stride(surface) / surface_bytes_per_pixel(surface)); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, + surface_width(surface), + surface_height(surface), + 0, surface->glformat, surface->gltype, + surface_data(surface)); + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +static void xb_surface_gl_destroy_texture(DisplaySurface *surface) +{ + if (!surface || !surface->texture) { + return; + } + glDeleteTextures(1, &surface->texture); + surface->texture = 0; +} + +static bool xb_console_gl_check_format(DisplayChangeListener *dcl, + pixman_format_code_t format) +{ + switch (format) { + case PIXMAN_BE_b8g8r8x8: + case PIXMAN_BE_b8g8r8a8: + case PIXMAN_r5g6b5: + return true; + default: + return false; + } +} + +static void gl_switch(DisplayChangeListener *dcl, + DisplaySurface *new_surface) +{ + struct xemu_console *scon = container_of(dcl, struct xemu_console, dcl); + scon->surface = new_surface; +} + +static float update_avg(float avg, float ms, float r) { + if (fabs(avg-ms) > 0.25*avg) avg = ms; + else avg = avg*(1.0-r)+ms*r; + return avg; +} + +static float fps = 1.0; + +static void update_fps(void) +{ + static float avg = 1.0; + static int64_t last_update = 0; + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME); + if (!last_update) { + last_update = now; + return; + } + float ms = ((float)(now-last_update)/1000000.0); + last_update = now; + avg = update_avg(avg, ms, 0.5); + fps = 1000.0/avg; +} + +static void process_vblank(struct xemu_console *scon) +{ + assert(bql_locked()); + + update_fps(); + +#if 0 + static uint64_t last_ns = 0; + uint64_t now_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME); + uint64_t delta_ns = last_ns ? now_ns - last_ns : 0; + fprintf(stderr, "%s delta_ns=%"PRId64"\n", __func__, delta_ns); + last_ns = now_ns; +#endif + + graphic_hw_update(scon->dcl.con); +} + +static void vblank_timer_callback(void *opaque) +{ + struct xemu_console *scon = (struct xemu_console *)opaque; + + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME); + process_vblank(scon); + timer_mod_ns(vblank_timer, now + vblank_interval_ns); +} + +static void *vblank_timer_thread(void *opaque) +{ + struct xemu_console *scon = (struct xemu_console *)opaque; + int64_t next_vblank = qemu_clock_get_ns(QEMU_CLOCK_REALTIME); + + while (true) { + // Schedule next vblank at fixed interval (absolute deadline) + next_vblank += vblank_interval_ns; + + // Wait until deadline + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME); + if (now < next_vblank) { + SDL_DelayPrecise(next_vblank - now); + } else if (now > next_vblank + vblank_interval_ns) { + // We've fallen behind by more than one frame, reset to avoid + // rapid-fire catch-up + next_vblank = now; + } + + xemu_main_loop_lock(); + process_vblank(scon); + xemu_main_loop_unlock(); + } + + return NULL; +} + +#if DEBUG_XEMU_C +static void report_stats(void) +{ + uint64_t now = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); + static uint64_t last_reported = 0; + static int num_frames = 0; + uint64_t delta_ms = now - last_reported; + num_frames += 1; + if (delta_ms >= 1000) { + DPRINTF("[[ "); + DPRINTF("vblank @%fHz avg", fps); + DPRINTF(" - bql %"PRId64"ns/iter, %g%% time avg", lock_held_acc/num_frames, (double)lock_held_acc/(double)(delta_ms * 10000.0)); + DPRINTF(" ]]\n"); + lock_held_acc = 0; + last_reported = now; + num_frames = 0; + } +} +#endif + +/** + * Renders the main interface. Usually called from the main thread, + * but may sometimes be called from another thread. + */ +static void gl_render_frame(struct xemu_console *scon) +{ + static bool rendering; + if (qatomic_xchg(&rendering, true)) { + return; + } + + SDL_GL_MakeCurrent(scon->real_window, scon->winctx); + + bool flip_required = false; + bool release_surface_texture = false; + + /* XXX: Note that this bypasses the usual VGA path in order to quickly + * get the surface. This is simple and fast, at the cost of accuracy. + * Ideally, this should go through the VGA code and opportunistically pull + * the surface like this, but handle the VGA logic as well. For now, just + * use this fast path to handle the common case. + * + * In the event the surface is not found in the surface cache, e.g. when + * the guest code isn't using HW accelerated rendering, but just blitting + * to the framebuffer, fall back to the VGA path. + */ + GLuint tex = nv2a_get_framebuffer_surface(); + + assert(glGetError() == GL_NO_ERROR); + + if (tex == 0) { + xemu_main_loop_lock(); + // FIXME: Don't upload if notdirty + xb_surface_gl_create_texture(scon->surface); + tex = scon->surface->texture; + flip_required = true; + release_surface_texture = true; + xemu_main_loop_unlock(); + } + + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + xemu_snapshots_set_framebuffer_texture(tex, flip_required); + xemu_hud_set_framebuffer_texture(tex, flip_required); + + /* FIXME: Finer locking. Event handlers in segments of the code expect + * to be running on the main thread with the BQL. For now, acquire the + * lock and perform rendering, but release before swap to avoid + * possible lengthy blocking (for vsync). + */ + xemu_main_loop_lock(); + xemu_hud_update(); + xemu_main_loop_unlock(); + + xemu_hud_render(); + glFinish(); + + if (release_surface_texture) { + xemu_main_loop_lock(); + xb_surface_gl_destroy_texture(scon->surface); + xemu_main_loop_unlock(); + } + + nv2a_release_framebuffer_surface(); + SDL_GL_SwapWindow(scon->real_window); + assert(glGetError() == GL_NO_ERROR); + + qatomic_set(&rendering, false); + +#if DEBUG_XEMU_C + report_stats(); +#endif +} + +static bool event_watch_callback(void *userdata, SDL_Event *event) +{ + struct xemu_console *scon = (struct xemu_console *)userdata; + + if (event->type == SDL_EVENT_WINDOW_EXPOSED || + event->type == SDL_EVENT_WINDOW_RESIZED) { + gl_render_frame(scon); + } + + return true; // Ignored +} + +static void poll_events(struct xemu_console *scon) +{ + SDL_Event ev1, *ev = &ev1; + bool allow_close = true; + + int kbd = 0, mouse = 0; + xemu_hud_should_capture_kbd_mouse(&kbd, &mouse); + + while (SDL_PollEvent(ev)) { + xemu_main_loop_lock(); + + // HUD must process events first so that if a controller is detached, + // a latent rebind request can cancel before the state is freed + xemu_hud_process_sdl_events(ev); + xemu_input_process_sdl_events(ev); + + switch (ev->type) { + case SDL_EVENT_KEY_DOWN: + if (kbd) break; + handle_keydown(ev); + break; + case SDL_EVENT_KEY_UP: + if (kbd) break; + handle_keyup(ev); + break; + case SDL_EVENT_QUIT: + if (scon->opts->has_window_close && !scon->opts->window_close) { + allow_close = false; + } + if (allow_close) { + shutdown_action = SHUTDOWN_ACTION_POWEROFF; + qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI); + } + break; + case SDL_EVENT_MOUSE_MOTION: + if (mouse) break; + handle_mousemotion(ev); + break; + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: + if (mouse) break; + handle_mousebutton(ev); + break; + case SDL_EVENT_MOUSE_WHEEL: + if (mouse) break; + handle_mousewheel(ev); + break; + case SDL_EVENT_WINDOW_FIRST ... SDL_EVENT_WINDOW_LAST: + handle_windowevent(ev); + break; + default: + break; + } + + xemu_main_loop_unlock(); + } + + xemu_main_loop_lock(); + xemu_input_update_controllers(); + xemu_main_loop_unlock(); +} static void display_very_early_init(DisplayOptions *o) { @@ -856,9 +1094,16 @@ static void display_early_init(DisplayOptions *o) SDL_GL_MakeCurrent(m_window, m_context); SDL_GL_SetSwapInterval(g_config.display.window.vsync ? 1 : 0); xemu_hud_init(m_window, m_context); - // blit = create_decal_shader(SHADER_TYPE_BLIT_GAMMA); } +static const DisplayChangeListenerOps dcl_gl_ops = { + .dpy_name = "xemu-gl", + .dpy_gfx_switch = gl_switch, + .dpy_gfx_check_format = xb_console_gl_check_format, + .dpy_mouse_set = mouse_warp, + .dpy_cursor_define = mouse_define, +}; + static void display_init(DisplayState *ds, DisplayOptions *o) { uint8_t data = 0; @@ -868,27 +1113,9 @@ static void display_init(DisplayState *ds, DisplayOptions *o) SDL_GL_MakeCurrent(m_window, m_context); gui_fullscreen = o->has_full_screen && o->full_screen; - gui_fullscreen |= g_config.display.window.fullscreen_on_startup; -#if 1 - // Explicitly set number of outputs to 1 for a single screen. We don't need - // multiple for now, but maybe in the future debug stuff can go on a second - // screen. num_outputs = 1; -#else - for (i = 0;; i++) { - QemuConsole *con = qemu_console_lookup_by_index(i); - if (!con) { - break; - } - } - num_outputs = i; - if (num_outputs == 0) { - return; - } -#endif - scon_list = g_new0(struct xemu_console, num_outputs); for (i = 0; i < num_outputs; i++) { QemuConsole *con = qemu_console_lookup_by_index(i); @@ -899,7 +1126,6 @@ static void display_init(DisplayState *ds, DisplayOptions *o) } scon_list[i].idx = i; scon_list[i].opts = o; - scon_list[i].opengl = 1; scon_list[i].dcl.ops = &dcl_gl_ops; scon_list[i].dcl.con = con; scon_list[i].kbd = qkbd_state_init(con); @@ -921,12 +1147,16 @@ static void display_init(DisplayState *ds, DisplayOptions *o) scon_list[0].real_window = m_window; scon_list[0].winctx = m_context; - mouse_mode_notifier.notify = sdl_mouse_mode_change; + mouse_mode_notifier.notify = mouse_mode_change; qemu_add_mouse_mode_change_notifier(&mouse_mode_notifier); sdl_cursor_hidden = SDL_CreateCursor(&data, &data, 8, 1, 0, 0); sdl_cursor_normal = SDL_GetCursor(); + // SDL_PollEvent may block during main window resize or drag operations. + // Register event watch to handle rendering during these operations. + SDL_AddEventWatch(event_watch_callback, &scon_list[0]); + /* 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); @@ -938,208 +1168,16 @@ static QemuDisplay qemu_display_xemu = { .init = display_init, }; -static void register_sdl1(void) +static void register_xemu_display(void) { qemu_display_register(&qemu_display_xemu); } -type_init(register_sdl1); - -void xb_surface_gl_create_texture(DisplaySurface *surface) -{ - assert(QEMU_IS_ALIGNED(surface_stride(surface), surface_bytes_per_pixel(surface))); - - switch (surface_format(surface)) { - case PIXMAN_BE_b8g8r8x8: - case PIXMAN_BE_b8g8r8a8: - surface->glformat = GL_BGRA_EXT; - surface->gltype = GL_UNSIGNED_BYTE; - break; - case PIXMAN_BE_x8r8g8b8: - case PIXMAN_BE_a8r8g8b8: - surface->glformat = GL_RGBA; - surface->gltype = GL_UNSIGNED_BYTE; - break; - case PIXMAN_r5g6b5: - surface->glformat = GL_RGB; - surface->gltype = GL_UNSIGNED_SHORT_5_6_5; - break; - default: - g_assert_not_reached(); - } - - if (!surface->texture) { - glGenTextures(1, &surface->texture); - } - glBindTexture(GL_TEXTURE_2D, surface->texture); - glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, - surface_stride(surface) / surface_bytes_per_pixel(surface)); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, - surface_width(surface), - surface_height(surface), - 0, surface->glformat, surface->gltype, - surface_data(surface)); - glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); -} - -void xb_surface_gl_destroy_texture(DisplaySurface *surface) -{ - if (!surface || !surface->texture) { - return; - } - glDeleteTextures(1, &surface->texture); - surface->texture = 0; -} - -static void gl_update(DisplayChangeListener *dcl, - int x, int y, int w, int h) -{ - struct xemu_console *scon = container_of(dcl, struct xemu_console, dcl); - assert(scon->opengl); - - SDL_GL_MakeCurrent(scon->real_window, scon->winctx); -} - -static void gl_switch(DisplayChangeListener *dcl, - DisplaySurface *new_surface) -{ - struct xemu_console *scon = container_of(dcl, struct xemu_console, dcl); - assert(scon->opengl); - SDL_GL_MakeCurrent(scon->real_window, scon->winctx); - xb_surface_gl_destroy_texture(scon->surface); - scon->surface = new_surface; - if (!new_surface) { - return; - } - - if (!scon->real_window) { - scon->real_window = m_window; - scon->winctx = m_context; - SDL_GL_MakeCurrent(scon->real_window, scon->winctx); - } -} - -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; -} - -static void gl_refresh(DisplayChangeListener *dcl) -{ - struct xemu_console *scon = container_of(dcl, struct xemu_console, dcl); - assert(scon->opengl); - bool flip_required = false; - - SDL_GL_MakeCurrent(scon->real_window, scon->winctx); - update_fps(); - - /* XXX: Note that this bypasses the usual VGA path in order to quickly - * get the surface. This is simple and fast, at the cost of accuracy. - * Ideally, this should go through the VGA code and opportunistically pull - * the surface like this, but handle the VGA logic as well. For now, just - * use this fast path to handle the common case. - * - * In the event the surface is not found in the surface cache, e.g. when - * the guest code isn't using HW accelerated rendering, but just blitting - * to the framebuffer, fall back to the VGA path. - */ - GLuint tex = nv2a_get_framebuffer_surface(); - if (tex == 0) { - // FIXME: Don't upload if notdirty - xb_surface_gl_create_texture(scon->surface); - scon->updates++; - tex = scon->surface->texture; - flip_required = true; - } - - /* FIXME: Finer locking. Event handlers in segments of the code expect - * to be running on the main thread with the BQL. For now, acquire the - * lock and perform rendering, but release before swap to avoid - * possible lengthy blocking (for vsync). - */ - qemu_mutex_lock_main_loop(); - bql_lock(); - poll_events(scon); - - glClearColor(0, 0, 0, 0); - glClear(GL_COLOR_BUFFER_BIT); - xemu_snapshots_set_framebuffer_texture(tex, flip_required); - xemu_hud_set_framebuffer_texture(tex, flip_required); - xemu_hud_render(); - - // Release BQL before swapping (which may sleep if swap interval is not immediate) - bql_unlock(); - qemu_mutex_unlock_main_loop(); - - glFinish(); - nv2a_release_framebuffer_surface(); - SDL_GL_SwapWindow(scon->real_window); - - /* VGA update (see note above) + vblank */ - qemu_mutex_lock_main_loop(); - bql_lock(); - graphic_hw_update(scon->dcl.con); - if (scon->updates && scon->surface) { - scon->updates = 0; - } - bql_unlock(); - qemu_mutex_unlock_main_loop(); - - /* Throttle to 60Hz */ - static int64_t last_update = 0; - int64_t deadline = last_update + 16666666; - int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME); - if (now < deadline) { - SDL_DelayPrecise(deadline - now); - } - last_update = qemu_clock_get_ns(QEMU_CLOCK_REALTIME); - -} - -static void gl_redraw(struct xemu_console *scon) -{ - assert(scon->opengl); - - if (scon->scanout_mode) { - assert(0); - /* gl_scanout_flush actually only care about - * the first argument. */ - // return gl_scanout_flush(&scon->dcl, 0, 0, 0, 0); - } - if (scon->surface) { - // xemu_sdl2_gl_render_surface(scon); - } -} - -static void process_key(struct xemu_console *scon, SDL_KeyboardEvent *ev) -{ - int qcode; - - if (ev->scancode >= qemu_input_map_usb_to_qcode_len) { - return; - } - qcode = qemu_input_map_usb_to_qcode[ev->scancode]; - qkbd_state_key_event(scon->kbd, qcode, ev->type == SDL_EVENT_KEY_DOWN); -} +type_init(register_xemu_display); int gArgc; char **gArgv; -// vl.c - static void *call_qemu_main(void *opaque) { int status; @@ -1286,7 +1324,7 @@ int main(int argc, char **argv) gui_grab = 0; if (gui_fullscreen) { - sdl_grab_start(0); + grab_start(0); set_full_screen(&scon_list[0], gui_fullscreen); } @@ -1295,23 +1333,26 @@ int main(int argc, char **argv) * to just run functions to avoid TLS bugs and locking issues. */ tcg_register_init_ctx(); - // rcu_register_thread(); qemu_set_current_aio_context(qemu_get_aio_context()); - DPRINTF("Main thread: initializing app\n"); - - qemu_mutex_lock_main_loop(); - bql_lock(); + xemu_main_loop_lock(); xemu_input_init(); - bql_unlock(); - qemu_mutex_unlock_main_loop(); + xemu_main_loop_unlock(); - while (1) { - gl_refresh(&scon_list[0].dcl); - assert(glGetError() == GL_NO_ERROR); + if (use_vblank_timer_thread) { + QemuThread vblank_thread; + qemu_thread_create(&vblank_thread, "vblank-timer", vblank_timer_thread, + &scon_list[0], QEMU_THREAD_DETACHED); + } else { + vblank_timer = timer_new_ns(QEMU_CLOCK_REALTIME, vblank_timer_callback, &scon_list[0]); + timer_mod_ns(vblank_timer, qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + vblank_interval_ns); } - // rcu_unregister_thread(); + while (1) { + struct xemu_console *scon = &scon_list[0]; + poll_events(scon); + gl_render_frame(scon); + } } void xemu_eject_disc(Error **errp) diff --git a/ui/xui/main.cc b/ui/xui/main.cc index b38c090427..ace02bc768 100644 --- a/ui/xui/main.cc +++ b/ui/xui/main.cc @@ -189,7 +189,7 @@ void xemu_hud_set_framebuffer_texture(GLuint tex, bool flip) g_flip_req = flip; } -void xemu_hud_render(void) +void xemu_hud_update(void) { ImGuiIO& io = ImGui::GetIO(); uint32_t now = SDL_GetTicks(); @@ -320,7 +320,10 @@ void xemu_hud_render(void) // static bool show_demo = true; // if (show_demo) ImGui::ShowDemoWindow(&show_demo); +} +void xemu_hud_render() +{ ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); diff --git a/ui/xui/misc.cc b/ui/xui/misc.cc index e6b34c8587..64b78a1cbe 100644 --- a/ui/xui/misc.cc +++ b/ui/xui/misc.cc @@ -30,7 +30,9 @@ static void RunOnMainThread(std::function &&func) [](void *userdata) { std::unique_ptr> f( static_cast *>(userdata)); + xemu_main_loop_lock(); (*f)(); + xemu_main_loop_unlock(); }, p, false)) { delete p; diff --git a/ui/xui/xemu-hud.h b/ui/xui/xemu-hud.h index 9ea86c4837..0e812fef94 100644 --- a/ui/xui/xemu-hud.h +++ b/ui/xui/xemu-hud.h @@ -36,10 +36,13 @@ void xemu_toggle_fullscreen(void); SDL_Window *xemu_get_window(void); void xemu_eject_disc(Error **errp); void xemu_load_disc(const char *path, Error **errp); +void xemu_main_loop_lock(void); +void xemu_main_loop_unlock(void); // Implemented in xemu_hud.cc void xemu_hud_init(SDL_Window *window, void *sdl_gl_context); void xemu_hud_cleanup(void); +void xemu_hud_update(void); void xemu_hud_render(void); void xemu_hud_process_sdl_events(SDL_Event *event); void xemu_hud_should_capture_kbd_mouse(int *kbd, int *mouse);