diff --git a/pkg/worker/caged/libretro/nanoarch/input.go b/pkg/worker/caged/libretro/nanoarch/input.go index 246988d4..e095bbd8 100644 --- a/pkg/worker/caged/libretro/nanoarch/input.go +++ b/pkg/worker/caged/libretro/nanoarch/input.go @@ -6,8 +6,16 @@ import ( "sync/atomic" ) -//#include -//#include "libretro.h" +/* +#include +#include "libretro.h" + +void input_cache_set_port(unsigned port, uint32_t buttons, + int16_t axis0, int16_t axis1, int16_t axis2, int16_t axis3); +void input_cache_set_keyboard_key(unsigned id, uint8_t pressed); +void input_cache_set_mouse(int16_t dx, int16_t dy, uint8_t buttons); +void input_cache_clear(void); +*/ import "C" const ( @@ -84,6 +92,19 @@ func (s *InputState) IsDpadTouched(port uint, axis uint) (shift C.int16_t) { return C.int16_t(atomic.LoadInt32(&s[port].axes[axis])) } +// SyncToCache syncs the entire input state to the C-side cache. +// Call this once before each Run() instead of having C call back into Go. +func (s *InputState) SyncToCache() { + for port := uint(0); port < maxPort; port++ { + buttons := atomic.LoadUint32(&s[port].keys) + axis0 := C.int16_t(atomic.LoadInt32(&s[port].axes[0])) + axis1 := C.int16_t(atomic.LoadInt32(&s[port].axes[1])) + axis2 := C.int16_t(atomic.LoadInt32(&s[port].axes[2])) + axis3 := C.int16_t(atomic.LoadInt32(&s[port].axes[3])) + C.input_cache_set_port(C.uint(port), C.uint32_t(buttons), axis0, axis1, axis2, axis3) + } +} + // SetKey sets keyboard state. // // 0 1 2 3 4 5 6 @@ -120,6 +141,15 @@ func (ks *KeyboardState) Pressed(key uint) C.int16_t { return Released } +// SyncToCache syncs keyboard state to the C-side cache. +func (ks *KeyboardState) SyncToCache() { + ks.mu.Lock() + defer ks.mu.Unlock() + for id, pressed := range ks.keys { + C.input_cache_set_keyboard_key(C.uint(id), C.uint8_t(pressed)) + } +} + // ShiftPos sets mouse relative position state. // // 0 1 2 3 @@ -148,3 +178,12 @@ func (ms *MouseState) Buttons() (l, r, m bool) { m = mbs&MouseMiddle != 0 return } + +// SyncToCache syncs mouse state to the C-side cache. +// This consumes the delta values (swaps to 0). +func (ms *MouseState) SyncToCache() { + dx := C.int16_t(ms.dx.Swap(0)) + dy := C.int16_t(ms.dy.Swap(0)) + buttons := C.uint8_t(ms.buttons.Load()) + C.input_cache_set_mouse(dx, dy, buttons) +} diff --git a/pkg/worker/caged/libretro/nanoarch/nanoarch.c b/pkg/worker/caged/libretro/nanoarch/nanoarch.c index 6290150c..d9010539 100644 --- a/pkg/worker/caged/libretro/nanoarch/nanoarch.c +++ b/pkg/worker/caged/libretro/nanoarch/nanoarch.c @@ -3,17 +3,18 @@ #include #include #include +#include #define RETRO_ENVIRONMENT_GET_CLEAR_ALL_THREAD_WAITS_CB (3 | 0x800000) int initialized = 0; typedef struct { - int type; - void* fn; - void* arg1; - void* arg2; - void* result; + int type; + void* fn; + void* arg1; + void* arg2; + void* result; } call_def_t; call_def_t call; @@ -26,6 +27,57 @@ enum call_type { void *same_thread_with_args(void *f, int type, ...); +// Input State Cache + +#define INPUT_MAX_PORTS 4 +#define INPUT_MAX_KEYS 512 + +typedef struct { + // Retropad: store raw button bitmask and analog axes per port + uint32_t buttons[INPUT_MAX_PORTS]; + int16_t analog[INPUT_MAX_PORTS][4]; // 4 axes per port + + // Keyboard + uint8_t keyboard[INPUT_MAX_KEYS]; + + // Mouse + int16_t mouse_x; + int16_t mouse_y; + uint8_t mouse_buttons; // bit 0=left, bit 1=right, bit 2=middle +} input_cache_t; + +static input_cache_t input_cache = {0}; + +// Update entire port state at once +void input_cache_set_port(unsigned port, uint32_t buttons, + int16_t axis0, int16_t axis1, int16_t axis2, int16_t axis3) { + if (port < INPUT_MAX_PORTS) { + input_cache.buttons[port] = buttons; + input_cache.analog[port][0] = axis0; + input_cache.analog[port][1] = axis1; + input_cache.analog[port][2] = axis2; + input_cache.analog[port][3] = axis3; + } +} + +// Keyboard update +void input_cache_set_keyboard_key(unsigned id, uint8_t pressed) { + if (id < INPUT_MAX_KEYS) { + input_cache.keyboard[id] = pressed; + } +} + +// Mouse update +void input_cache_set_mouse(int16_t dx, int16_t dy, uint8_t buttons) { + input_cache.mouse_x = dx; + input_cache.mouse_y = dy; + input_cache.mouse_buttons = buttons; +} + +void input_cache_clear(void) { + memset(&input_cache, 0, sizeof(input_cache)); +} + void core_log_cgo(enum retro_log_level level, const char *fmt, ...) { char msg[2048] = {0}; va_list va; @@ -144,8 +196,61 @@ void core_input_poll_cgo() { } int16_t core_input_state_cgo(unsigned port, unsigned device, unsigned index, unsigned id) { - int16_t coreInputState(unsigned, unsigned, unsigned, unsigned); - return coreInputState(port, device, index, id); + if (port >= INPUT_MAX_PORTS) { + return 0; + } + + switch (device) { + case RETRO_DEVICE_JOYPAD: + // Extract button bit from cached bitmask + return (int16_t)((input_cache.buttons[port] >> id) & 1); + + case RETRO_DEVICE_ANALOG: + switch (index) { + case RETRO_DEVICE_INDEX_ANALOG_LEFT: + // id: 0=X, 1=Y + if (id < 2) { + return input_cache.analog[port][id]; + } + break; + case RETRO_DEVICE_INDEX_ANALOG_RIGHT: + // id: 0=X, 1=Y -> stored in axes[2], axes[3] + if (id < 2) { + return input_cache.analog[port][2 + id]; + } + break; + } + break; + + case RETRO_DEVICE_KEYBOARD: + if (id < INPUT_MAX_KEYS) { + return input_cache.keyboard[id] ? 1 : 0; + } + break; + + case RETRO_DEVICE_MOUSE: + switch (id) { + case RETRO_DEVICE_ID_MOUSE_X: { + int16_t x = input_cache.mouse_x; + input_cache.mouse_x = 0; // Consume delta + return x; + } + case RETRO_DEVICE_ID_MOUSE_Y: { + int16_t y = input_cache.mouse_y; + input_cache.mouse_y = 0; // Consume delta + return y; + } + case RETRO_DEVICE_ID_MOUSE_LEFT: + return (input_cache.mouse_buttons & 0x01) ? 1 : 0; + case RETRO_DEVICE_ID_MOUSE_RIGHT: + return (input_cache.mouse_buttons & 0x02) ? 1 : 0; + case RETRO_DEVICE_ID_MOUSE_MIDDLE: + return (input_cache.mouse_buttons & 0x04) ? 1 : 0; + } + break; + } + + return 0; } size_t core_audio_sample_batch_cgo(const int16_t *data, size_t frames) { diff --git a/pkg/worker/caged/libretro/nanoarch/nanoarch.go b/pkg/worker/caged/libretro/nanoarch/nanoarch.go index 7c05d962..e0be87fd 100644 --- a/pkg/worker/caged/libretro/nanoarch/nanoarch.go +++ b/pkg/worker/caged/libretro/nanoarch/nanoarch.go @@ -396,12 +396,21 @@ func (n *Nanoarch) Reset() { C.bridge_call(retroReset) } +func (n *Nanoarch) syncInputToCache() { + n.retropad.SyncToCache() + if n.keyboardCb != nil { + n.keyboard.SyncToCache() + } + n.mouse.SyncToCache() +} + func (n *Nanoarch) Run() { + n.syncInputToCache() + if n.LibCo { C.same_thread(retroRun) } else { if n.Video.gl.enabled { - // running inside a go routine, lock the thread to make sure the OpenGL context stays current runtime.LockOSThread() if err := n.sdlCtx.BindContext(); err != nil { n.log.Error().Err(err).Msg("ctx bind fail") @@ -672,59 +681,6 @@ func coreVideoRefresh(data unsafe.Pointer, width, height uint, packed uint) { Nan0.Handlers.OnVideo(data_, int32(dt), FrameInfo{W: width, H: height, Stride: packed}) } -//export coreInputState -func coreInputState(port C.unsigned, device C.unsigned, index C.unsigned, id C.unsigned) C.int16_t { - //Nan0.log.Debug().Msgf("%v %v %v %v", port, device, index, id) - - // something like PCSX-ReArmed has 8 ports - if port >= maxPort { - return Released - } - - switch device { - case C.RETRO_DEVICE_JOYPAD: - return Nan0.retropad.IsKeyPressed(uint(port), int(id)) - case C.RETRO_DEVICE_ANALOG: - switch index { - case C.RETRO_DEVICE_INDEX_ANALOG_LEFT: - return Nan0.retropad.IsDpadTouched(uint(port), uint(index*2+id)) - case C.RETRO_DEVICE_INDEX_ANALOG_RIGHT: - case C.RETRO_DEVICE_INDEX_ANALOG_BUTTON: - } - case C.RETRO_DEVICE_KEYBOARD: - return Nan0.keyboard.Pressed(uint(id)) - case C.RETRO_DEVICE_MOUSE: - switch id { - case C.RETRO_DEVICE_ID_MOUSE_X: - x := Nan0.mouse.PopX() - return x - case C.RETRO_DEVICE_ID_MOUSE_Y: - y := Nan0.mouse.PopY() - return y - case C.RETRO_DEVICE_ID_MOUSE_LEFT: - if l, _, _ := Nan0.mouse.Buttons(); l { - return Pressed - } - case C.RETRO_DEVICE_ID_MOUSE_RIGHT: - if _, r, _ := Nan0.mouse.Buttons(); r { - return Pressed - } - case C.RETRO_DEVICE_ID_MOUSE_WHEELUP: - case C.RETRO_DEVICE_ID_MOUSE_WHEELDOWN: - case C.RETRO_DEVICE_ID_MOUSE_MIDDLE: - if _, _, m := Nan0.mouse.Buttons(); m { - return Pressed - } - case C.RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELUP: - case C.RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELDOWN: - case C.RETRO_DEVICE_ID_MOUSE_BUTTON_4: - case C.RETRO_DEVICE_ID_MOUSE_BUTTON_5: - } - } - - return Released -} - //export coreAudioSampleBatch func coreAudioSampleBatch(data unsafe.Pointer, frames C.size_t) C.size_t { if Nan0.Stopped.Load() {