diff --git a/android/app/src/main/java/com/izzy2lost/x1box/ControllerInputBridge.kt b/android/app/src/main/java/com/izzy2lost/x1box/ControllerInputBridge.kt index b0c5686924..89d9a1f84c 100644 --- a/android/app/src/main/java/com/izzy2lost/x1box/ControllerInputBridge.kt +++ b/android/app/src/main/java/com/izzy2lost/x1box/ControllerInputBridge.kt @@ -1,5 +1,8 @@ package com.izzy2lost.x1box +import android.os.Handler +import android.os.Looper +import android.os.SystemClock import android.view.KeyEvent import org.libsdl.app.SDLControllerManager @@ -8,9 +11,16 @@ import org.libsdl.app.SDLControllerManager */ class ControllerInputBridge : OnScreenController.ControllerListener { + private data class ButtonDispatchState( + var isDown: Boolean = false, + var pressedAtMs: Long = 0L, + var pendingRelease: Runnable? = null, + ) + companion object { // Virtual device ID for on-screen controller const val VIRTUAL_DEVICE_ID = -2 + private const val MIN_TAP_HOLD_MS = 50L // Axis indices for SDL const val AXIS_LEFT_X = 0 @@ -21,33 +31,57 @@ class ControllerInputBridge : OnScreenController.ControllerListener { const val AXIS_RIGHT_TRIGGER = 5 } + private val mainHandler = Handler(Looper.getMainLooper()) + private val buttonStates = mutableMapOf() + override fun onButtonPressed(button: OnScreenController.Button) { + val state = buttonStates.getOrPut(button) { ButtonDispatchState() } + state.pendingRelease?.let(mainHandler::removeCallbacks) + state.pendingRelease = null + state.pressedAtMs = SystemClock.uptimeMillis() + if (state.isDown) { + return + } + state.isDown = true + try { - when (button) { - OnScreenController.Button.LEFT_TRIGGER, - OnScreenController.Button.RIGHT_TRIGGER -> - setTriggerState(button, pressed = true) - else -> { - val keyCode = getKeyCodeForButton(button) - SDLControllerManager.onNativePadDown(VIRTUAL_DEVICE_ID, keyCode) - } - } + dispatchButtonState(button, pressed = true) } catch (e: Exception) { DebugLog.e("ControllerBridge", e) { "Error on button press: ${e.message}" } } } override fun onButtonReleased(button: OnScreenController.Button) { - try { - when (button) { - OnScreenController.Button.LEFT_TRIGGER, - OnScreenController.Button.RIGHT_TRIGGER -> - setTriggerState(button, pressed = false) - else -> { - val keyCode = getKeyCodeForButton(button) - SDLControllerManager.onNativePadUp(VIRTUAL_DEVICE_ID, keyCode) + val state = buttonStates.getOrPut(button) { ButtonDispatchState() } + state.pendingRelease?.let(mainHandler::removeCallbacks) + state.pendingRelease = null + if (!state.isDown) { + return + } + + val elapsed = SystemClock.uptimeMillis() - state.pressedAtMs + val remaining = MIN_TAP_HOLD_MS - elapsed + if (remaining > 0L) { + val releaseTask = Runnable { + state.pendingRelease = null + if (!state.isDown) { + return@Runnable + } + state.isDown = false + try { + dispatchButtonState(button, pressed = false) + } catch (e: Exception) { + DebugLog.e("ControllerBridge", e) { "Error on delayed button release: ${e.message}" } } } + state.pendingRelease = releaseTask + mainHandler.postDelayed(releaseTask, remaining) + return + } + + state.isDown = false + try { + dispatchButtonState(button, pressed = false) } catch (e: Exception) { DebugLog.e("ControllerBridge", e) { "Error on button release: ${e.message}" } } @@ -94,6 +128,38 @@ class ControllerInputBridge : OnScreenController.ControllerListener { } } + fun reset() { + buttonStates.forEach { (button, state) -> + state.pendingRelease?.let(mainHandler::removeCallbacks) + state.pendingRelease = null + if (!state.isDown) { + return@forEach + } + state.isDown = false + try { + dispatchButtonState(button, pressed = false) + } catch (e: Exception) { + DebugLog.e("ControllerBridge", e) { "Error resetting $button: ${e.message}" } + } + } + } + + private fun dispatchButtonState(button: OnScreenController.Button, pressed: Boolean) { + when (button) { + OnScreenController.Button.LEFT_TRIGGER, + OnScreenController.Button.RIGHT_TRIGGER -> + setTriggerState(button, pressed) + else -> { + val keyCode = getKeyCodeForButton(button) + if (pressed) { + SDLControllerManager.onNativePadDown(VIRTUAL_DEVICE_ID, keyCode) + } else { + SDLControllerManager.onNativePadUp(VIRTUAL_DEVICE_ID, keyCode) + } + } + } + } + private fun setTriggerState(button: OnScreenController.Button, pressed: Boolean) { val keyCode = getKeyCodeForButton(button) val axis = when (button) { diff --git a/android/app/src/main/java/com/izzy2lost/x1box/MainActivity.kt b/android/app/src/main/java/com/izzy2lost/x1box/MainActivity.kt index 1878a0f7ce..120291c1bf 100644 --- a/android/app/src/main/java/com/izzy2lost/x1box/MainActivity.kt +++ b/android/app/src/main/java/com/izzy2lost/x1box/MainActivity.kt @@ -99,6 +99,7 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { // gesture panel, notification shade, or dialog appears). Without this, // triggers can stay "pressed" if the touch UP event is never delivered. onScreenController?.resetAllInputs() + controllerBridge?.reset() } } @@ -218,6 +219,7 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { override fun onPause() { swipeUpGestureRecognizer.reset() onScreenController?.resetAllInputs() + controllerBridge?.reset() super.onPause() } diff --git a/hw/xbox/mcpx/apu/apu.c b/hw/xbox/mcpx/apu/apu.c index c1e05cb368..f61ec7d96b 100644 --- a/hw/xbox/mcpx/apu/apu.c +++ b/hw/xbox/mcpx/apu/apu.c @@ -264,6 +264,22 @@ static int getenv_int_clamped(const char *name, int min_value, int max_value, return (int)parsed; } +static void monitor_hold_last_sample(MCPXAPUState *s, uint8_t *stream, int len) +{ + int frame_bytes = sizeof(s->monitor.last_output_sample); + int frames = len / frame_bytes; + int16_t *out = (int16_t *)stream; + for (int i = 0; i < frames; i++) { + out[i * 2 + 0] = s->monitor.last_output_sample[0]; + out[i * 2 + 1] = s->monitor.last_output_sample[1]; + } + + int tail_bytes = len - (frames * frame_bytes); + if (tail_bytes > 0) { + memset(stream + (frames * frame_bytes), 0, tail_bytes); + } +} + static void monitor_sink_cb(void *opaque, uint8_t *stream, int free_b) { MCPXAPUState *s = MCPX_APU_DEVICE(opaque); @@ -274,7 +290,12 @@ static void monitor_sink_cb(void *opaque, uint8_t *stream, int free_b) } int avail = 0; - for (int i = 0; i < 10; i++) { +#ifdef __ANDROID__ + int wait_attempts = 24; +#else + int wait_attempts = 10; +#endif + for (int i = 0; i < wait_attempts; i++) { qemu_spin_lock(&s->monitor.fifo_lock); avail = fifo8_num_used(&s->monitor.fifo); qemu_spin_unlock(&s->monitor.fifo_lock); @@ -304,7 +325,14 @@ static void monitor_sink_cb(void *opaque, uint8_t *stream, int free_b) } if (copied < free_b) { - memset(stream + copied, 0, free_b - copied); + monitor_hold_last_sample(s, stream + copied, free_b - copied); + } + + if (free_b >= sizeof(s->monitor.last_output_sample)) { + int16_t *last = (int16_t *)(stream + free_b - + sizeof(s->monitor.last_output_sample)); + s->monitor.last_output_sample[0] = last[0]; + s->monitor.last_output_sample[1] = last[1]; } qemu_cond_broadcast(&s->cond); @@ -317,11 +345,16 @@ static void monitor_init(MCPXAPUState *d) d->monitor.device_buffer_bytes = 0; d->monitor.queued_bytes_low = 0; d->monitor.queued_bytes_high = 0; + d->monitor.last_output_sample[0] = 0; + d->monitor.last_output_sample[1] = 0; int fifo_frames = 3; int audio_samples = 512; #ifdef __ANDROID__ - fifo_frames = 16; + /* Give Android a little more audio headroom to ride out short stalls + * without increasing the device callback size again. + */ + fifo_frames = 24; audio_samples = 2048; fifo_frames = getenv_int_clamped("XEMU_ANDROID_AUDIO_FIFO_FRAMES", 3, 32, fifo_frames); diff --git a/hw/xbox/mcpx/apu/apu_int.h b/hw/xbox/mcpx/apu/apu_int.h index f7221924bc..0ffc6640b9 100644 --- a/hw/xbox/mcpx/apu/apu_int.h +++ b/hw/xbox/mcpx/apu/apu_int.h @@ -102,6 +102,7 @@ typedef struct MCPXAPUState { struct { McpxApuDebugMonitorPoint point; int16_t frame_buf[256][2]; // 1 EP frame (0x400 bytes), 8 buffered + int16_t last_output_sample[2]; QemuSpin fifo_lock; Fifo8 fifo; int fifo_capacity_bytes; diff --git a/hw/xbox/mcpx/apu/vp/vp.c b/hw/xbox/mcpx/apu/vp/vp.c index eed2c9b1e4..3d776969d3 100644 --- a/hw/xbox/mcpx/apu/vp/vp.c +++ b/hw/xbox/mcpx/apu/vp/vp.c @@ -1751,6 +1751,43 @@ voice_work_dispatch(MCPXAPUState *d, qemu_mutex_lock(&vwd->lock); if (vwd->queue_len) { + if (vwd->num_workers == 1) { + VoiceWorker *worker = &vwd->workers[0]; + int voice_count = vwd->queue_len; + + memset(worker->mixbins, 0, sizeof(worker->mixbins)); + if (d->monitor.point == MCPX_APU_DEBUG_MON_VP) { + memset(worker->sample_buf, 0, sizeof(worker->sample_buf)); + } + + for (int i = 0; i < vwd->queue_len; i++) { + voice_process(d, worker->mixbins, worker->sample_buf, + vwd->queue[i].voice, vwd->queue[i].list); + } + + voice_work_release_voice_locks(d); + vwd->queue_len = 0; + + for (int b = 0; b < NUM_MIXBINS; b++) { + for (int s = 0; s < NUM_SAMPLES_PER_FRAME; s++) { + mixbins[b][s] += worker->mixbins[b][s]; + } + } + if (d->monitor.point == MCPX_APU_DEBUG_MON_VP) { + for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) { + d->vp.sample_buf[i][0] += worker->sample_buf[i][0]; + d->vp.sample_buf[i][1] += worker->sample_buf[i][1]; + } + } + + g_dbg.vp.workers[0].num_voices = voice_count; + g_dbg.vp.workers[0].time_us = + qemu_clock_get_us(QEMU_CLOCK_REALTIME) - start_time; + g_dbg.vp.total_worker_time_us = g_dbg.vp.workers[0].time_us; + qemu_mutex_unlock(&vwd->lock); + return; + } + memset(vwd->mixbins, 0, sizeof(vwd->mixbins)); // Signal workers and wait for completion @@ -1782,8 +1819,9 @@ static int mcpx_apu_default_vp_worker_count(void) #ifdef __ANDROID__ /* * Mobile SoCs are often oversubscribed already (TCG + render + I/O). - * Keep VP worker defaults conservative to reduce thread contention and - * audio underruns; allow explicit override via env. + * Keep VP worker defaults very conservative to reduce thread contention and + * leave more CPU time for the emulation threads that gate frametime. + * Allow explicit override via env. */ const char *value = getenv("XEMU_ANDROID_VP_WORKERS"); if (value && value[0] != '\0') { @@ -1794,16 +1832,10 @@ static int mcpx_apu_default_vp_worker_count(void) } } - if (cpu_count <= 2) { + if (cpu_count <= 6) { return 1; } - if (cpu_count <= 4) { - return 2; - } - if (cpu_count <= 6) { - return 3; - } - return 4; + return 2; #else return cpu_count; #endif @@ -1829,6 +1861,10 @@ static void voice_work_init(MCPXAPUState *d) qemu_mutex_lock(&vwd->lock); qemu_cond_init(&vwd->work_pending); qemu_cond_init(&vwd->work_finished); + if (vwd->num_workers == 1) { + qemu_mutex_unlock(&vwd->lock); + return; + } for (int i = 0; i < vwd->num_workers; i++) { vwd->workers_pending |= 1 << i; qemu_thread_create(&vwd->workers[i].thread, "mcpx.voice_worker", @@ -1843,6 +1879,12 @@ static void voice_work_finalize(MCPXAPUState *d) { VoiceWorkDispatch *vwd = &d->vp.voice_work_dispatch; + if (vwd->num_workers == 1) { + g_free(vwd->workers); + vwd->workers = NULL; + return; + } + qemu_mutex_lock(&vwd->lock); vwd->workers_should_exit = true; qemu_cond_broadcast(&vwd->work_pending); diff --git a/hw/xbox/nv2a/pgraph/gl/renderer.c b/hw/xbox/nv2a/pgraph/gl/renderer.c index cdc094a177..2b93f3d363 100644 --- a/hw/xbox/nv2a/pgraph/gl/renderer.c +++ b/hw/xbox/nv2a/pgraph/gl/renderer.c @@ -180,7 +180,7 @@ static void pgraph_gl_finalize(NV2AState *d) static void pgraph_gl_flip_stall(NV2AState *d) { NV2A_GL_DFRAME_TERMINATOR(); - glFinish(); + glFlush(); } static void pgraph_gl_flush(NV2AState *d) diff --git a/hw/xbox/nv2a/pgraph/gl/renderer.h b/hw/xbox/nv2a/pgraph/gl/renderer.h index 695c8cd0ad..872098b813 100644 --- a/hw/xbox/nv2a/pgraph/gl/renderer.h +++ b/hw/xbox/nv2a/pgraph/gl/renderer.h @@ -193,6 +193,11 @@ typedef struct PGRAPHGLState { bool download_dirty_surfaces_pending; QemuEvent dirty_surfaces_download_complete; // common +#ifdef __ANDROID__ + GLuint gl_download_pbo; + size_t gl_download_pbo_size; +#endif + TextureBinding *texture_binding[NV2A_MAX_TEXTURES]; Lru texture_cache; TextureLruNode *texture_cache_entries; diff --git a/hw/xbox/nv2a/pgraph/gl/surface.c b/hw/xbox/nv2a/pgraph/gl/surface.c index 20b69b616f..e2e027e04b 100644 --- a/hw/xbox/nv2a/pgraph/gl/surface.c +++ b/hw/xbox/nv2a/pgraph/gl/surface.c @@ -92,6 +92,54 @@ static bool android_drain_gl_errors_silent(void) return had_error; } +static void android_glo_readpixels(PGRAPHGLState *r, GLenum gl_format, + GLenum gl_type, + unsigned int bytes_per_pixel, + unsigned int stride, + unsigned int width, + unsigned int height, bool vflip, + void *data) +{ + size_t required_size = stride * height; + glBindBuffer(GL_PIXEL_PACK_BUFFER, r->gl_download_pbo); + if (required_size > r->gl_download_pbo_size) { + glBufferData(GL_PIXEL_PACK_BUFFER, required_size, NULL, + GL_STREAM_READ); + r->gl_download_pbo_size = required_size; + } + + int rl, pa; + glGetIntegerv(GL_PACK_ROW_LENGTH, &rl); + glGetIntegerv(GL_PACK_ALIGNMENT, &pa); + glPixelStorei(GL_PACK_ROW_LENGTH, stride / bytes_per_pixel); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + glReadPixels(0, 0, width, height, gl_format, gl_type, 0); + + void *mapped_data = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, + required_size, GL_MAP_READ_BIT); + if (mapped_data) { + if (vflip) { + GLubyte *b = (GLubyte *) data; + GLubyte *c = + &((GLubyte *) mapped_data)[stride * (height - 1)]; + for (unsigned int irow = 0; irow < height; irow++) { + memcpy(b, c, width * bytes_per_pixel); + b += stride; + c -= stride; + } + } else { + memcpy(data, mapped_data, required_size); + } + glUnmapBuffer(GL_PIXEL_PACK_BUFFER); + } + + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + + glPixelStorei(GL_PACK_ROW_LENGTH, rl); + glPixelStorei(GL_PACK_ALIGNMENT, pa); +} + static void android_sanitize_surface_format(PGRAPHGLState *r, SurfaceFormatInfo *fmt) { @@ -1375,8 +1423,8 @@ static bool android_surface_download_depth16_to_guest(NV2AState *d, } rgba_pixels = g_malloc(read_height * rgba_stride); - glo_readpixels(GL_RGBA, GL_UNSIGNED_BYTE, 4, rgba_stride, read_width, - read_height, flip, rgba_pixels); + android_glo_readpixels(r, GL_RGBA, GL_UNSIGNED_BYTE, 4, rgba_stride, + read_width, read_height, flip, rgba_pixels); if (android_log_surface_download_errors( "surface_download_depth16: pack read", surface)) { @@ -1533,8 +1581,9 @@ static bool android_surface_download_z24s8_to_guest(NV2AState *d, } depth_pixels = g_malloc(read_width * read_height * 4); - glo_readpixels(GL_RGBA, GL_UNSIGNED_BYTE, 4, read_width * 4, read_width, - read_height, flip, depth_pixels); + android_glo_readpixels(r, GL_RGBA, GL_UNSIGNED_BYTE, 4, + read_width * 4, read_width, read_height, flip, + depth_pixels); if (android_log_surface_download_errors( "surface_download_z24s8: post-read-depth-pack", surface)) { goto cleanup; @@ -1553,9 +1602,9 @@ static bool android_surface_download_z24s8_to_guest(NV2AState *d, } stencil_pixels = g_malloc0(read_width * read_height); - glo_readpixels(GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, 1, read_width, - read_width, read_height, flip, - stencil_pixels); + android_glo_readpixels(r, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, 1, + read_width, read_width, read_height, flip, + stencil_pixels); if (android_drain_gl_errors_silent()) { if (!warned_stencil_readback_unsupported) { warned_stencil_readback_unsupported = true; @@ -1713,8 +1762,9 @@ static void surface_download_to_buffer(NV2AState *d, SurfaceBinding *surface, uint8_t *rgba_linear = rgba_pixels; uint8_t *linear_guest = pixels; - glo_readpixels(GL_RGBA, GL_UNSIGNED_BYTE, 4, rgba_stride, - read_width, read_height, flip, rgba_pixels); + android_glo_readpixels(pg->gl_renderer_state, GL_RGBA, + GL_UNSIGNED_BYTE, 4, rgba_stride, + read_width, read_height, flip, rgba_pixels); android_log_surface_download_errors( "surface_download_to_buffer: post-read_rgba8", surface); @@ -1768,7 +1818,11 @@ static void surface_download_to_buffer(NV2AState *d, SurfaceBinding *surface, gl_read_buf = pg->scale_buf; } +#ifdef __ANDROID__ + android_glo_readpixels(pg->gl_renderer_state, +#else glo_readpixels( +#endif surface->fmt.gl_format, surface->fmt.gl_type, surface->fmt.bytes_per_pixel, pg->surface_scale_factor * surface->pitch, pg->surface_scale_factor * surface->width, @@ -2520,6 +2574,10 @@ void pgraph_gl_init_surfaces(PGRAPHState *pg) pgraph_gl_reload_surface_scale_factor(pg); glGenFramebuffers(1, &r->gl_framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, r->gl_framebuffer); +#ifdef __ANDROID__ + glGenBuffers(1, &r->gl_download_pbo); + r->gl_download_pbo_size = 0; +#endif QTAILQ_INIT(&r->surfaces); r->downloads_pending = false; qemu_event_init(&r->downloads_complete, false); @@ -2558,6 +2616,14 @@ void pgraph_gl_finalize_surfaces(PGRAPHState *pg) glDeleteFramebuffers(1, &r->gl_framebuffer); r->gl_framebuffer = 0; +#ifdef __ANDROID__ + if (r->gl_download_pbo) { + glDeleteBuffers(1, &r->gl_download_pbo); + r->gl_download_pbo = 0; + r->gl_download_pbo_size = 0; + } +#endif + finalize_render_to_texture(pg); } diff --git a/ui/xemu-input.c b/ui/xemu-input.c index 5cfe345636..e50e83a570 100644 --- a/ui/xemu-input.c +++ b/ui/xemu-input.c @@ -48,6 +48,7 @@ #define XEMU_INPUT_MIN_INPUT_UPDATE_INTERVAL_US 2500 #define XEMU_INPUT_MIN_RUMBLE_UPDATE_INTERVAL_US 2500 +#define XEMU_INPUT_MIN_BUTTON_HOLD_US 50000 #if 0 static void xemu_input_print_controller_state(ControllerState *state) @@ -92,6 +93,58 @@ const char *bound_drivers[4] = { DRIVER_DUKE, DRIVER_DUKE, DRIVER_DUKE, DRIVER_DUKE }; int test_mode; +static ControllerState *xemu_input_find_sdl_controller(SDL_JoystickID id) +{ + ControllerState *iter; + + QTAILQ_FOREACH(iter, &available_controllers, entry) { + if (iter->type == INPUT_DEVICE_SDL_GAMECONTROLLER && + iter->sdl_joystick_id == id) { + return iter; + } + } + + return NULL; +} + +static int xemu_input_sdl_button_to_button_id(uint8_t button) +{ + switch (button) { + case SDL_CONTROLLER_BUTTON_A: + return 0; + case SDL_CONTROLLER_BUTTON_B: + return 1; + case SDL_CONTROLLER_BUTTON_X: + return 2; + case SDL_CONTROLLER_BUTTON_Y: + return 3; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + return 4; + case SDL_CONTROLLER_BUTTON_DPAD_UP: + return 5; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + return 6; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + return 7; + case SDL_CONTROLLER_BUTTON_BACK: + return 8; + case SDL_CONTROLLER_BUTTON_START: + return 9; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + return 10; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + return 11; + case SDL_CONTROLLER_BUTTON_LEFTSTICK: + return 12; + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: + return 13; + case SDL_CONTROLLER_BUTTON_GUIDE: + return 14; + default: + return -1; + } +} + static const char **port_index_to_settings_key_map[] = { &g_config.input.bindings.port1, &g_config.input.bindings.port2, @@ -444,6 +497,17 @@ void xemu_input_process_sdl_events(const SDL_Event *event) xemu_queue_notification(buf); xemu_input_rebind_xmu(port); } + } else if (event->type == SDL_CONTROLLERBUTTONDOWN) { + ControllerState *state = + xemu_input_find_sdl_controller(event->cbutton.which); + int button_id = + xemu_input_sdl_button_to_button_id(event->cbutton.button); + + if (state && button_id >= 0) { + state->button_hold_until_us[button_id] = + qemu_clock_get_us(QEMU_CLOCK_REALTIME) + + XEMU_INPUT_MIN_BUTTON_HOLD_US; + } } else if (event->type == SDL_CONTROLLERDEVICEREMOVED) { DPRINTF("Controller Removed: %d\n", event->cdevice.which); int handled = 0; @@ -575,6 +639,8 @@ void xemu_input_update_sdl_kbd_controller_state(ControllerState *state) void xemu_input_update_sdl_controller_state(ControllerState *state) { + int64_t now = qemu_clock_get_us(QEMU_CLOCK_REALTIME); + state->buttons = 0; memset(state->axis, 0, sizeof(state->axis)); if (!state->controller_map) { @@ -641,6 +707,14 @@ void xemu_input_update_sdl_controller_state(ControllerState *state) #undef INVERT_AXIS + for (int i = 0; i < 15; i++) { + if (state->button_hold_until_us[i] > now) { + state->buttons |= CONTROLLER_STATE_BUTTON_ID_TO_MASK(i); + } else { + state->button_hold_until_us[i] = 0; + } + } + // xemu_input_print_controller_state(state); } diff --git a/ui/xemu-input.h b/ui/xemu-input.h index 8b1a742732..a85c71ecd0 100644 --- a/ui/xemu-input.h +++ b/ui/xemu-input.h @@ -89,6 +89,7 @@ typedef struct ControllerState { // Input state uint16_t buttons; int16_t axis[CONTROLLER_AXIS__COUNT]; + int64_t button_hold_until_us[15]; // Rendering state hacked on here for convenience but needs to be moved (FIXME) uint32_t animate_guide_button_end; diff --git a/ui/xemu.c b/ui/xemu.c index 41c683bb06..2e66bf451b 100644 --- a/ui/xemu.c +++ b/ui/xemu.c @@ -1381,7 +1381,7 @@ void xemu_android_display_loop(void) } #ifdef __ANDROID__ xemu_android_refresh_frame_limit_from_env(); - SDL_GL_SetSwapInterval(g_config.display.window.vsync ? 1 : 0); + SDL_GL_SetSwapInterval(0); xemu_hud_init(m_window, m_context); #endif tcg_register_init_ctx(); @@ -1737,10 +1737,14 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl) */ static int64_t last_update = 0; #ifdef __ANDROID__ - int64_t deadline = last_update + g_android_frame_interval_ns; + const int64_t frame_interval = g_android_frame_interval_ns; #else - int64_t deadline = last_update + 16666666; + const int64_t frame_interval = 16666666; #endif + if (last_update == 0) { + last_update = qemu_clock_get_ns(QEMU_CLOCK_REALTIME); + } + int64_t deadline = last_update + frame_interval; #ifdef DEBUG_XEMU_C int64_t sleep_acc = 0; @@ -1775,7 +1779,11 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl) } } else { DPRINTF("zzZz %g %ld\n", (double)sleep_acc/1000000.0, spin_acc); - last_update = now; + if (now - deadline > frame_interval) { + last_update = now; + } else { + last_update = deadline; + } break; } }