input: a port's RetroPad buttons are compiled once a frame

A core that reads its buttons one by one went through
input_state_internal() for each: the port mapping, the remap table,
turbo, hold, the overlay and analog-to-d-pad, sixteen times a frame
for one pad. A core that asks for the bitmask got all sixteen from one
such walk.

Keep the result of that walk as the frame's view of the port: one
word holding the sixteen buttons as the core is to see them. It is
compiled through the existing mask path - nothing about how a button
is decided changes - when a core asks for the port's mask, or for a
second button in the frame. After that every button and mask query
for the port reads the word. input_driver_poll() invalidates it, and
so does a core's first analog request, which switches analog-to-d-pad
off for the port.

The first button of a frame still takes the old path, so a core that
reads a single button a frame pays what it did.

In samples/runloop/frontend_overhead, on the release build:

- sixteen buttons read one by one: about 65 ns a query before, about
  25 ns now with the compile spread over them;
- 256 queries a frame: 8 ns each;
- one query a frame: unchanged, 2.7 us for the whole frame against
  2.9 us before, which is within the noise of this machine.

The lane added in the previous commit holds this to what cores saw
before: reading button by button and reading the mask give the same
buttons frame for frame, and the same recorded digests, under the
default mapping, a remap, analog-to-d-pad, turbo and hold. The
digests did not change.

The port a core passes is now checked before it indexes the per-port
arrays here; one past MAX_USERS read and wrote outside them.

Not covered by a test: run-ahead, netplay and input recording. All
three reach this function the same way a core does - recording still
logs every query - and each run of the core polls, which invalidates
the view. Turbo modes other than the classic one are not in the
lane either.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-10-02 23:43:03 +02:00
1 parent 932aa6a984
commit 3a6a1e9c4f
2 files changed
+76 -4

No files matched your search

+64 -4
View File
@@ -7853,6 +7853,10 @@ void input_driver_poll(void)
/* Invalidate joypad state bitmask cache for the new frame */
memset(input_st->joypad_state_cache_valid, 0,
sizeof(input_st->joypad_state_cache_valid));
memset(input_st->frame_view_valid, 0,
sizeof(input_st->frame_view_valid));
memset(input_st->frame_view_asked, 0,
sizeof(input_st->frame_view_asked));
/* Enable/disable sensors at the driver level based on demand
* from shaders and/or core. Setting gates everything. */
@@ -8483,20 +8487,76 @@ int16_t input_driver_state_wrapper(unsigned port, unsigned device,
*input_st = &input_driver_st;
settings_t *settings = config_get_ptr();
int16_t result = 0;
/* The port comes from the core, and indexes per-port arrays here
* and below. */
if (port >= MAX_USERS)
return 0;
#ifdef HAVE_BSV_MOVIE
if (BSV_MOVIE_IS_PLAYBACK_ON())
return bsv_movie_read_state(input_st, port, device, idx, id);
#endif
/* Read input state */
result = input_state_internal(input_st, settings, port, device, idx, id);
/* Read input state.
*
* The RetroPad's buttons come from the frame's view of the port: a
* word holding all sixteen, compiled once through the mask path of
* input_state_internal(). After that a button or a mask query is
* a read of that word, however many a core makes and whichever way
* it asks.
*
* A core that reads button by button used to walk the port
* mapping, remaps, turbo, hold and overlay once per button. The
* view is compiled on its second button of the frame, not its
* first: a core that reads a single button a frame keeps the cost
* it had, and one that reads more pays for the walk once. A core
* that asks for the mask already got its buttons compiled this
* way. samples/runloop/frontend_overhead holds the two readings
* to the same result, frame for frame. */
if ( (device & RETRO_DEVICE_MASK) == RETRO_DEVICE_JOYPAD
&& idx == 0
&& ( id < RARCH_FIRST_CUSTOM_BIND
|| id == RETRO_DEVICE_ID_JOYPAD_MASK))
{
if ( !input_st->frame_view_valid[port]
&& ( id == RETRO_DEVICE_ID_JOYPAD_MASK
|| input_st->frame_view_asked[port]))
{
input_st->frame_view_joypad[port] = input_state_internal(
input_st, settings, port, RETRO_DEVICE_JOYPAD, 0,
RETRO_DEVICE_ID_JOYPAD_MASK);
input_st->frame_view_valid[port] = true;
}
if (!input_st->frame_view_valid[port])
{
/* the frame's first button for this port */
input_st->frame_view_asked[port] = true;
result = input_state_internal(input_st, settings,
port, device, idx, id);
}
else if (id == RETRO_DEVICE_ID_JOYPAD_MASK)
result = input_st->frame_view_joypad[port];
else
result = ((uint16_t)input_st->frame_view_joypad[port] >> id) & 1;
}
else
result = input_state_internal(input_st, settings, port, device, idx, id);
/* Register any analog stick input requests for
* this 'virtual' (core) port */
* this 'virtual' (core) port. The first one switches
* analog-to-d-pad off for the port, which changes its buttons:
* the view is compiled again for the queries that follow. */
if ( (device == RETRO_DEVICE_ANALOG)
&& ( (idx == RETRO_DEVICE_INDEX_ANALOG_LEFT)
|| (idx == RETRO_DEVICE_INDEX_ANALOG_RIGHT)))
|| (idx == RETRO_DEVICE_INDEX_ANALOG_RIGHT))
&& !input_st->analog_requested[port])
{
input_st->analog_requested[port] = true;
input_st->frame_view_valid[port] = false;
input_st->frame_view_asked[port] = false;
}
#ifdef HAVE_BSV_MOVIE
if (BSV_MOVIE_IS_RECORDING())
+12
View File
@@ -696,7 +696,19 @@ typedef struct
* armed outside input_keys_pressed() starts from the previous
* frame's held set. */
uint16_t wait_release_mask[MAX_USERS];
/* The frame's view of each port's RetroPad buttons, as a core is
* given them: after port mapping, remaps, turbo, hold, overlays and
* analog-to-d-pad. Compiled when a core asks for the port's mask,
* or for a second button in the frame (frame_view_asked marks the
* first); from then on every button and mask query for the port
* reads this word. Invalidated by input_driver_poll(), and when
* the core's first analog request changes analog-to-d-pad for the
* port. */
int16_t frame_view_joypad[MAX_USERS];
bool joypad_state_cache_valid[MAX_USERS];
bool frame_view_valid[MAX_USERS];
bool frame_view_asked[MAX_USERS];
retro_bits_512_t keyboard_mapping_bits; /* bool alignment */
input_game_focus_state_t game_focus_state; /* bool alignment */