Commit Graph
14 Commits
Author SHA1 Message Date
Davey Hughes 56952c9a25 Android: follow-up fixes for screens on other displays (gl and vulkan) (#20044)
* rgui: upload the menu again to the video driver a reinit makes

Closing content with RGUI up left the screen black until a key press.
The staged content load presents frames between its stages, so RGUI
can upload its framebuffer to the old video driver and clear FB_DIRTY
just before the driver stage replaces that driver. The new one has no
menu texture, nothing in the menu changes, and RGUI never uploads
again.

command_event_reinit sets FB_DIRTY after its reinit, but the staged
load and video-only resets reach the menu only through context_reset,
so RGUI marks its framebuffer dirty there. The menu_icon_ownership
harness gets an rgui lane that reinits the video driver and checks
the next upload reaches the new one.

* video: draw a frame queued before a view count shrink with its views

Under threaded video the wrapper ran SET_VIEW_COUNT ahead of the frame
it had already claimed, so a closing core's last queued frame drew
after its view chains were gone: whole, both screens stacked, until the
menu's first frame replaced it.

The wrapper still answers at once, but a smaller count reaches the
driver only once the frames queued before it have drawn. A grow applies
at once as before, with the main thread waiting for the preset parse.
Vulkan and glcore therefore build view chains only when the count
grows, so a deferred shrink never parses a preset. They also keep the
headset's whole-frame chain across a count change: it does not depend
on the count, and with chains built only on a grow nothing would
rebuild it after a shrink to 0.

* android: don't make a second screen's EGL surface once Java took it back

Java can take the second screen's surface back between the poll that
hands it over and the frame that makes its EGL surface. Skip making one
then; the screens go back to the main window as they do when a make
fails. Java can still take the surface back during the make itself; the
node's own ANativeWindow reference keeps the window alive through that.

* android: ask a display move by display id and number it

A move named its display by its place in the list, and Java looked
that up later on the UI thread against whatever list was newest, so a
display coming or going in between could send RetroArch elsewhere.
Java now gets the display id and fails the move when that display is
gone. Native code now counts the ids when it compares display lists,
so a display unplugged and plugged back is a new list and its failures
clear.

A failure was a bare flag, so one move's failure could end the next
move and mark its display unable. Each move now carries a number and a
failure counts only for the move it names. The move code also loses
two stores that were always overwritten.

* android: end the refocus hold after a move only when it is done

A move onto a display that is no longer valid now fails at once
instead of waiting out its timeout. The hold that keeps RetroArch's
window unfocusable after a move ends only on the focus loss Android
answers it with, not on a gain that was already queued; an earlier
hold ends only once the new move has started; and onStop() ends a
running hold rather than leaving it to its timer.

* video_windows: tell an external monitor by its display type

Android lists the AYN Thor's two panels and a DP monitor with the same
unknown connection type, so Auto took the monitor for a second panel:
on a one-screen phone Game Screen 2 went to it. Java now reads the
hidden Display.getType() by reflection where it can and passes
internal or external with each display. A display typed internal is
never a monitor; where the type is unknown, a non-default display with
an unknown connection and a product name now counts as one.

* video: give a Presentation on another display its own UI wait

The menu waits up to a second for the Presentation it is laid out for
to show, but the wait was only counted from the first one. Once a
Presentation had shown, one made on another display had no wait left
and the menu fell back to the main window at once.

* input: keep the menu's mouse where it was while the menu is in a window

With Mouse Support on, the menu moving into the Presentation read as a
mouse move to (0,0) for a frame, which marks the pointer active there.
The view keeps the main window's last mouse position instead.

* input: match a window's touches only from a windowed publication

A publication without windows after one with them left the old window
ids in place, so a window's touch lookup could still match them. Only
touches published in a window match now. No Android path publishes
that way today.

* vulkan: copy a waiting window's views on a new frame

A screen window that was up but had no free image on a new frame did
not get its views copied, so a later repeat that drew it, as a pause
with the menu there does, left black areas where those views go. They
are copied on every new frame now.

* video: load the idle logo from the assets directory without widgets

The idle logo's path came from the widgets, which only start with
On-Screen Notifications on, so with them off the idle window stayed
black. Without the widgets' path it is built from the assets
directory the way the widgets build it.

* android: hold the focus when Android moves RetroArch between displays

When the display RetroArch is on, or moving to, goes away, Android
moves the Activity to the default display and can send that display's
focus gain before the removed display's loss. A task moved to another
display and straight back gets no configuration change, and can read
the old display's loss last too. With the loss last, RetroArch pauses
while Android keeps it focused, and no later focus event wakes it.

Count these as moves, as RetroArch's own moves are: a removal, and any
change of RetroArch's display it did not ask for. From Android 10, with
more than one display, so is losing and regaining the top-resumed place
while focused, or regaining it unfocused within a second of losing it
after a loss no hold asked for. The refocus hold that covers a move's
late loss then covers these too: the window is made unfocusable until
Android has taken the focus, then gets the gain again. A gain already on
its way skips the hold, and a move marked while RetroArch is focused
ends after a second with no focus event.

* samples: close test gaps in video_windows and android_focus

The is_default check set the flag and read it back, so it could not
fail; Auto's fallback to the default display now shows the flag at
work. Cover a display listed without an ordinal, and display keys at
sizes 0 to 3, two unnamed displays and a 63-character name. Drop a
stale note from android_focus_test's header.

* video: fix wording left over from the display rows

The Screen Display sublabel still named the Separate mode, a setting
the display rows removed; it now says "when it has a window of its
own". The warnings for a screen window that can't be drawn, or whose
surface is gone, said its screens go back to the main window, but a
window that shows only the menu reaches them too. They now read like
the one for a window that can't be made: what it shows stays in the
main window.

Comments that no longer matched the code are reworded. pointer_frame's
model of the driver before the publication is named was_* rather than
today_*, and a wrong commit hash and a plan tag go from the tests.
video_driver_free_internal() uses VIDEO_ANDROID_RELEASE_KEPT_WINDOWS()
rather than open-coding it, with no change in behaviour.
2026-11-09 18:59:16 +01:00
LibretroAdmin 7ddff7122e input: the drivers with touches write them into the frontend's places
Raw input, DirectInput, Wayland, SDL3 and Android built their touches
on the stack at each poll and the frontend copied them in, Wayland
sixteen of them every poll whether or not any screen was touched. They
write the places the frontend lends them now.
2026-11-09 09:10:54 +01:00
LibretroAdmin 801182903b input: the drivers with one mouse write it into the frontend's frame too
SDL, DirectInput, Wayland, win32 and Android built their mouse on the
stack at each poll and the frontend copied it in. They write the frame
it lends them now, as X11 and raw input do.
2026-11-09 08:54:11 +01:00
LibretroAdmin 917e193c96 input: X11 and raw input write their mice into the frontend's frames
The two drivers with several mice built them on the stack at each poll
and the frontend copied them into its own. They now write the frontend's
frames in place, lent by input_driver_pointers_lend(), and publishing
them copies nothing. The drivers with one mouse keep their copy of it.
2026-11-09 08:46:59 +01:00
davey d94e1409cd android: publish the mouse and every touch to the frontend
The touches on the main window and on the second display go to the
frontend's pointer publication each poll, raw, each with the window it
is in, and the publication answers the pointer, the screen pointer,
the second display's pointer and a lightgun aimed by touch. The mouse
stays the driver's: its motion and wheel are taken by each read, as
before.

Every answer is as it was; the pointer_frame test checks each one the
publication gives against the driver's own answers before this change.
Three things differ. Binds to the mouse's left, right and middle buttons
now work: they never fired on Android. And a touch is placed at the
first read after the poll instead of as its event came, so a finger held
still while the viewport changes follows the change at once.

And a tap on a main window that shows no screen is on nothing: it fires
no lightgun button there, while a tap in the letterbox still does.
2026-11-07 21:28:27 +01:00
davey 1e0f4db5d9 input: let a driver keep its own pointer answers
Android's input driver is to publish its touches and mouse through the
frontend's pointer publication, so that each touch carries the window
it came from, without changing any answer a core or the menu gets
from it. Five flags let a driver ask for that, and change nothing for
the drivers that publish today: a touch pressed only inside what it
is read against; touches in a screen's own window as a second list;
the mouse answered by the driver, whose motion and wheel go with each
read; touch indices as slots that keep their place, with no mouse
standing in; and pointer ids the frontend does not hold left to the
driver.

The publication also holds 32 touches, and gives the menu a touch in
its own window over that window's client area.
2026-11-07 21:28:27 +01:00
Davey Hughesandlibretroadmin 74b7b06fa5 Android second display output for video views (gl and vulkan) (#19905)
* android: drain the sensor queue with both sensors off

Losing focus disables the accelerometer and gyroscope, but an event
already in the sensor queue stays there, and android_input_poll_user()
only read the queue while a sensor was on. The queue's fd stays
readable, so ALooper_pollOnce() keeps returning LOOPER_ID_USER and
android_input_poll() spins at full CPU. The pause and stop
acknowledgements wait for the next runloop iteration, which never
comes: RetroArch logs "App thread did not acknowledge activity state",
the launcher shows late and the app hangs until it gets focus back.
A swipe home hits it often, since focus is lost before onPause.

Always drain the queue and apply the events only while a sensor is on.

* gl2: make a regrown pass texture as the pass's first one

gl2_recreate_fbo() gave the larger texture no filter, so it kept GL's
default GL_NEAREST_MIPMAP_LINEAR with a single level. On GLES that
texture is incomplete, and once a viewport-scaled pass grew, the next
pass sampled black; desktop GL with texture storage gets a complete
one-level texture. Make it with gl2_create_fbo_texture(), as at init, so
it also keeps the pass's filter, wrap mode and float or sRGB format. The
feedback texture is regrown the same way.

* video: per-screen window settings and the screens' window plan

Each screen gets window settings in the config,
video_screenN_window_mode, _x, _y, _width, _height, _monitor and
_fullscreen: Attached, Separate or Hidden, and where a window of its own
goes and how big it is. Screen 1 is always Attached; the others default
to Separate. video_windows_enable, off by default, is the switch that
lets screens leave the main window at all.

video_windows_plan() turns those settings into a frame's plan: the
screens the main window draws, the ones that go to windows of their own
and the hidden ones. A screen stays in the main window while the switch
is off, where the driver has no windows, or once the renderer has sent
it back. The views layout takes exclude_screens, so the main window lays
out only the screens it keeps, and a window only its own.

This is the base the desktop secondary windows and the Android second
display share. The video_windows harness, run in the Linux gfx samples
workflow, checks the plan and a layout with screens left out.

* video: contract for secondary screens' native windows

A context can offer windows of their own for secondary core screens
through an optional gfx_ctx_window_driver_t, the last member of
gfx_ctx_driver_t: create, update, poll and destroy, and bind and
present for GL contexts. Requests, native handles and events carry a
generation from video_driver_window_generation(), a frontend counter
that a driver or context restart does not reset, so an event that
outlives its window cannot be taken for its replacement's.

GFX_CTX_FLAGS_VIDEO_WINDOWS is what a video driver will report once it
draws to such windows; nothing sets it yet.

* egl: window surfaces for secondary screens

egl_window_surface_new/bind/swap/free give a secondary screen's window
a surface on the main context's display and config, with the main
window's attributes. Bind makes the context current on it at swap
interval 0, or on the main surface again; free puts the main surface
back first where the freed one is current. None of them log.

* video: share the secondary windows' event bookkeeping

What a native layer keeps of each secondary window for the frontend -
its owner, record, request, position, the events waiting one bit per
kind, and whether it closed - is a video_window_common_t at the start
of its own state, and the rules on it live once in gfx/video_windows.c:
marking a window closed (CLOSE, with FOCUS_LOSS where it had the focus,
a waiting gain or resume dropped), poll's drain in its fixed order with
whatever has no room left waiting, and the sweep of an owner's windows
at context teardown.

* video: one placement clamp for secondary windows

video_window_place_rect() puts saved bounds on a monitor or its work
area: bounds partly on it are moved onto it, automatic ones and those
not on it at all are centred, and the size is clamped. Whether bounds
are on it is judged as saved, before the size is clamped, so an
oversized window partly on it keeps its position on the other axis.

* video: frame snapshot for secondary windows

A frame presented with views now carries what a driver needs to make,
place and lay out the screens' own windows without reading settings:
each screen's window settings, the window plan, the core's own map and
frame size, the layout parameters and a restore count, from which
video_driver_window_layout() lays out one screen's window. The snapshot
lives in video_driver_state_t and video_frame_info_t points at it, NULL
while windows are off or the driver cannot make them; the threaded
wrapper copies it into the frame's slot, as it does the statistics text.

While the driver can make windows, the main window's layout leaves out
the screens that have one of their own or are hidden.
video_driver_restore_windows() clears the screens a close attached and
counts the request, and window generations can be taken on any thread.

* video: published layouts and translate for secondary windows

A renderer publishes each secondary window's layout as drawn, with the
core map it was made from and the window's client size, through
video_driver_publish_window_layout() from the thread that runs the
context. Latest wins with no lock: each window has three slots handed
over by index exchange, as the window title is, and NULL clears it.

video_driver_translate_window_point() maps a point in a window to the
core's frame: window 0 through the viewport, as before, and a secondary
through its published layout, scaled from client to drawn pixels. A
point in a window that has no layout, was cleared or has another
generation is not mapped.

* input: window-aware pointer and touch publication

input_driver_publish_window_pointers() and _touches() take each device's
and each touch's window: 0 for the main window, else a screen's own with
its generation. A device in a screen's window is placed through that
window's layout for the pointer and the lightgun, and has no place on
the main window's screen; one whose window is gone reads off-screen.
Relative motion, buttons and the ports' devices are as they were.

input_driver_publish_pointers() and _touches() publish into the main
window, so the input drivers that call them need no change.

* samples: video_screens harness for the screens' own windows

The shared windows base is tested against the shipping configuration,
video and input objects: per-screen window settings across saves and
overrides, a screen's own layout, the frame snapshot and its copy
under the threaded wrapper, published layouts and point translation,
window-aware pointers and touches, and window generations. build.py
links the test with a configured non-Qt build's objects; the OpenXR
desktop jobs, which make one, run it.

* video: the window plan can keep a screen other than 0 attached

video_windows_plan() takes the screen that stays in the main window
whatever its mode, and screen 0 takes that screen's mode. Callers pass
0, which plans as before. Android's external monitor rule needs it:
screen 0 goes to the monitor and screen 1 stays on the device, where
the touch screen is.

* video: route the separate screens to a platform's displays

video_windows_route() chooses the display a platform's Separate
screens go to, from a list the platform publishes. A screen's monitor
setting N names the display of ordinal N; 0 is Auto, which takes an
on, valid and physical display that is neither private nor the main
window's: an external one (DIRECT or TRANSITIVE) first, then the
lowest ordinal. On an external monitor Auto puts screen 0 there and
keeps screen 1, so the touch screen stays on the device. One display
takes screens at a time; a screen bound for another stays attached.
Each display also carries its size, for the menu's display picker.

* samples: a touch through a second output's layout

A touch on a second display's surface, mapped through the layout drawn
there, lands in the touched screen's part of the packed frame: one
screen on a 1240x1080 panel, two screens sharing it, and screen 0 on a
monitor; the letterbox reads off-screen when asked.

* samples: pin the attached screen, picker and pillar cases

The plan keeps a screen other than 1 attached with screen 0 in its
mode, and falls back to Attached with no settings. The picker refuses a
private display but takes one that is off, and Auto never takes an
entry without an ordinal. A touch in a pillar beside two screens, not
reported off-screen, is held to the nearer one.

* video: route the screens' windows through a platform's display list

A platform publishes its displays from its UI thread, latest wins,
through three slots as the window layouts are handed over. Each frame
takes the newest list, routes the Separate screens with
video_windows_route() and plans with the screen it keeps attached; a
screen with nowhere to go stays in the main window. All screens bound
for one display share one window, led by the lowest one, and the frame
says which screens each window shows. Taking a list restores the screens
a failed window sent back, and video_driver_attach_windows() lets a
renderer send them back from any thread. A request names its display,
and Android gets a native window kind.

A screenshot or GPU recording through the threaded wrapper draws the
cached frame again on the video thread. That redraw takes no list: it
keeps the app thread's last route, as it reads the forced and restore
state, so a renderer keeps the second display's window and the capture
shows the right screens. The route is written only when it changes, so
Win32, which routes nothing and whose modal redraw runs beside the app
thread, never writes it while it is read.

The video_screens harness redraws the cached frame through the real
threaded wrapper and checks that it keeps the frame's route and plan.

* video: let a platform take the secondary windows back

video_driver_windows_retire() has the renderer let go of every
secondary window, through a new optional poke hook. It is for a
platform command such as Android's second surface going away, never a
frame. With the threaded wrapper up it is queued to the video thread
behind the frame being drawn, and nothing waits for it: a hook the
caller passes runs there just before and just after, for the platform
to hold its own requests back and to hear when it is done. Without the
wrapper all of it runs before the call returns. One is queued at a
time; false says the video thread cannot take it yet.

video_screens checks that a retire queued behind a frame being drawn
returns at once, and runs after the frame, between the hook's calls.

* video: one slot hand-over for the window layouts and displays

The window layouts and the display list are handed over the same way,
through three slots. Two small helpers publish and take a slot for both,
with the same memory orders. No behaviour change.

The harness adds two display list checks: a longer list keeps its first
VIDEO_WINDOW_DISPLAYS_MAX, and a get of capacity 1 fills only the first
entry.

* android: native side of the second display

The Java controller hands native code the display list and the second
display's surface through three natives. The list goes to
video_driver_publish_window_displays(). A surface becomes a node holding
one ANativeWindow reference, exchanged in for the video thread to take,
with a generation the touches carry. A size change of the same window
keeps the generation; a different window first gives up the old one.
Giving one up uses the main window's ticket handoff: the UI thread waits
while the app thread drops the node and queues the renderer's letting go
of its windows. The thread that lets go of them acknowledges once it is
done, so the UI thread is not left waiting for the app thread's next
poll; a retire the video thread cannot take yet is tried again from the
input poll. Native code asks Java to show or hide the display through
setSecondDisplay(), which posts and never waits, and asks nothing while
the renderer is letting go of a surface. The app state gets a 64-record
ring for the display's touches.

The natives run on the UI thread and read its own app state,
g_android_early, which android_app_free() clears unless a re-created
activity has already replaced it, so a callback after onDestroy finds
none. A display name GetStringUTFChars cannot read leaves no exception
pending for the next JNI call.

* android: second display windows for the gl and vulkan contexts

Both Android contexts give the renderer the secondary-window table.
Creating a window asks Java to show the display it is routed to and
returns unconfigured. Until Java publishes its first display list a
Separate screen resolves to display 0, which names no display, so its
window is refused and the renderer keeps the screen attached until a
list arrives. An update takes only the request's settings; another
screen or request generation is a new window, the renderer's to make.
Its poll hands it the newest surface native code received, raising
GEOMETRY and publishing which window and surface the input poll maps
touches through. The EGL context makes the window's surface on the main
context the first time it is bound after a surface arrives, at swap
interval 0, with the native visual id queried once at init, since the
query logs on failure and binding sits on the present path; the Vulkan
context gives the renderer the ANativeWindow and leaves the surface to
it.

Each request native code sends Java carries a number, and each surface
comes back with the number of the request it was made for. Destroying
the window drops the surface it held, then asks Java to hide the
display. When it was the last window, that hide gives up every surface
made for an earlier request, including one that reaches native code
after it: no window takes one, and its surfaceDestroyed frees what is
left and returns without the ticket handoff. A hide native code starts,
for closing or switching content or reinitialising the video driver,
never holds the UI thread.

* android: touches on the second display reach the core

Each touch on the second display crosses from the UI thread in the ring
with the generation of the surface it was on. nativeTouch sits with the
other second display natives and reads the UI thread's own app state,
which android_app_free() clears. A down or a move goes in only when it
leaves room for an up of every finger the input driver tracks, and an up
only when its down went in, so a full ring never drops a lift and leaves
a finger pressed for the core. MAX_TOUCH moves to platform_unix.h for
the native and the input driver to share.

The input poll drops the touches of any surface but the one the renderer
shows, maps the rest through that window's published layout into the
core's frame, and keeps the fingers after the main display's in the
core's pointer list. They never move the mouse, count as a quick tap or
reach the frontend's own screen pointer, so the menu and overlay stay on
the main display.

* input: the second display's fingers on a device of their own

With an overlay's pointer on, or one of its buttons held, the frontend
answers RETRO_DEVICE_POINTER from the overlay or not at all, and a
windowed overlay polls that device as its own touches. So the second
display's fingers cannot share it: the Android driver answers them on a
frontend-only RARCH_DEVICE_POINTER_SECOND and keeps RETRO_DEVICE_POINTER
to the main display. The frontend lists them for the core after the main
list's pointers, whoever answers for those: the overlay, none while the
pointer is blocked, or the driver. A driver with no second list answers
with its main list alone.

The pointer_second harness checks those answers against a stand-in
driver with both lists, through input_state_device() and the overlay's
pointer answer extracted from input_driver.c.

* android: a Presentation for the second display

SecondDisplayController lists the displays for native code - whether
each is the Activity's own, valid, on, private, a real panel or monitor
(product info, API 31+), its connection, name and size - and shows a
Presentation with a SurfaceView on the display native code asks for. The
window is not focusable and not touch modal and the view is not
focusable, so touching it neither pauses the core nor takes the gamepad;
it keeps the screen on and, on API 29+, keeps system gestures off it. It
follows the Activity from onStart to onStop, holding a request made
while stopped, and a frame after a display or configuration change
publishes the list again when it changed. A display it cannot show on
stays invalid until the list changes, so the display events a
Presentation coming or going may fire do not retry the failing show in a
loop. Each Presentation carries the number of the show request it was
made for and hands its surfaces to native code with it; touches go with
the surface's generation.

* android: second display failures and Presentations Android dismisses

Every display a show failed on stays invalid until the list changes or
the Activity starts again. A single failed id would not do: of two
displays that both refuse a Presentation, marking one failed makes the
other valid again, native code routes back to it, and the two take turns
failing forever. Starting again retries a one-off failure when the app
returns.

Android cancels a Presentation itself when its display's metrics change
(API 17-29). A dismiss we did not make clears it, so the dead
Presentation does not leave its display counted as shown. While native
code still wants that display, it is shown on again if the dismissed
Presentation's view had a surface. One cancelled before that cancels
itself, for example on stale display metrics, and rebuilding it would
loop at message rate; it counts as a failed show, so its display is
published invalid until the list changes or the Activity starts again.

* video: which screen windows a renderer makes and lets go each frame

video_windows_steps() tells a renderer, from one frame's snapshot,
which of its windows to let go of (no screens now, or another
display) and which to make. A window that closed or failed is not
made again until the restore count moves, so frames already queued
do not bring it straight back. Renderers share the rule instead of
each keeping its own.

* gl2: views draw into a given output; the preset's targets can grow

gl2_views_render() takes the layout, size and canvas it draws with
instead of reading the main window's, and the loop that grows the
preset's targets on a resize becomes a function of its own. Nothing
changes for the main window; a screen's own window will draw through
both.

* gl2: draw the screens' own windows

With a context that can make windows of their own for screens, gl2
reports GFX_CTX_FLAGS_VIDEO_WINDOWS while it may draw views. Each frame
it makes and lets go of the windows the frame's snapshot asks for and
draws each one's screens through the views compositor, with the preset's
passes where one is loaded. A window's pass targets grow only once its
surface is bound, so a failed bind has nothing to undo. A window is
drawn after the main window's frame and swapped at interval 0 before the
main swap, which stays the only one that can wait. The menu, OSD,
widgets and overlay stay in the main window.

A window that cannot be made or drawn hands its screens back to the main
window until the next restore and logs once. A window its context closes
hands them back too, and a restore that lands in the same frame does not
make it again; one closed with no screens left is not marked failed, so
it can be made again when its screens come back before the next restore.
The poke's windows_retire lets go of every window at once, for a
platform that takes a surface back.

The gl2_pbo_readback and gl_last_pass_fbo samples include gl2.c; neither
draws windows, so their shared stubs answer the frontend's window calls
with no-ops.

* samples: views test core logs a second finger, and asks for GLES 3

With a finger on each of two displays the second display's finger is
pointer 1. The core reads, maps and logs pointers 0 and 1 through one
helper, each with its own last state; pointer 0's lines and crosshair
are unchanged. Android's gl driver only gives GLES contexts, so a gles3
value of video_views_test_hw lets the core render through hardware there
too. gles3 with the checker or noise pattern logs an error at load:
those patterns upload a BGRA texture, which GLES 3.0 does not take.

* vulkan: the window-system surface switch as a helper

vulkan_surface_create() made the VkSurfaceKHR for each window system
in line. The switch moves into vulkan_wsi_surface_create(), which
takes the instance and returns the surface, so a second output on the
same device can make its own surface the same way. VULKAN_WSI_DISPLAY
stays where it was, since it needs the context's GPU. No behaviour
change.

* vulkan: a second output on the context's device

A vulkan_output_t is a surface of its own with a swapchain on the
context's device and queue, for a screen shown on another display. It
never waits in a frame. The acquire takes no time and uses a fence,
never a semaphore, so nothing the second display's presentation engine
signals ever enters the queue the main output's frames share. The fence
is polled, and an image not yet free is kept for the next frame. Out of
date remakes the swapchain at once; suboptimal remakes it once and then
keeps it, as the main chain does. On Android the swapchain takes the
size the window reports, since the surface's currentExtent can lag an
in-place resize and Android scales any extent to the window. A lost
surface is dropped. A swapchain let go of is destroyed once a fence
queued behind its last present has signalled. Only teardown waits, on
the output's own fences.

The harness runs the real code against a fake device and checks that no
frame-path call waits, that only completed objects are destroyed, and
that everything made is freed.

* vulkan: draw screens in windows of their own

Where the context has a windows table (Android's second display), a
screen the plan sends to a window of its own is drawn there with its
views' chains, sized for the window, and the frame's own view copies.
The window is drawn in the frame's command buffer, after the main pass
and before the chains end their frame. Its image comes from a fence
already signalled, so the frame's submission waits on nothing new; it
signals the window's semaphore, and the window presents after the
submission and before the main present, which stays the only one that
waits. The menu, OSD, widgets and overlay stay in the main window.

A window that can't be made, drawn or kept puts its screens back in
the main window until the next restore. One whose output is not done
yet is let go of on a later frame; the poke's windows_retire and the
driver's teardown wait for it, on the output's own fences.

* video: separate screen windows on by default on Android

On Android a Separate screen goes to a second display, which is what a
device with two panels is for, so the switch starts on there. Desktop
windows stay opt-in. The settings table records the Android default;
the harness checks that an override carries the switch and a screen's
mode.

* menu: a window mode and a display for each screen from 2 on

Each screen past the first gets two settings rows: Attached, Separate or
Hidden, and the display it goes to when it is Separate. The display
picker offers Auto and the displays the platform has published, shown as
the monitor index is ("2 (Screen-2 1240x1080)"), and keeps a display set
elsewhere until it is changed. It widens only up to the most displays
that can be published, so a hand-edited value in the thousands cannot
build a list that long on the main thread. The row's sublabel says that
the display RetroArch is on keeps the screen attached. Each screen's
rows have their own enum, as the per-port input rows do, so the dropdown
and the sublabel find the right one. A change to any of the rows gives a
screen the renderer sent back to the main window another try.

The settings harness checks the rows, the left key's clamp, and that a
change to any of them restores closed windows.

* menu: show the screens' window rows where the driver has windows

Stereo 3D & Screens shows Separate Screen Windows when the video
driver reports secondary windows. While it is on, it also shows the
mode and display of screen 2 and of each further screen the core has.
On Android the switch reads "Show secondary screens on another
display". A screen row's dropdown takes that row's label as its
title.

* android: show the second display again after its metrics change

On Android 10, a density or size change on the second display left it
empty until the app restarted. Android cancels a Presentation when its
display's metrics change, and the one made to replace it got the
display's resources from a cache keyed by display id, still holding the
old metrics. Presentation.onStart() found them stale and cancelled it
before it drew, so the display was marked failed and both screens stayed
on the main display.

Bring the new Presentation's resources up to the display's current
metrics before showing it. The refresh runs only before Android 12; from
then on a Presentation has a window context of its own.

* android: read input queue keys and sticks through their fields

The key and stick helpers of the input queue take the few fields
they read instead of the event, and the default loop's slot lookup
and key path become functions of their own. Nothing changes for the
queue; the second display's window can then hand its keys and stick
moves to the same code.

* android: a focus rule for the Activity and the second display's window

Once the second display's window can take focus, RetroArch's focus
comes from two windows. Where each display has its own focus, the
second window keeps its display's focus while the keys go elsewhere,
so it counts only while in use: after a key, stick move or touch from
it, a focus it gained while the Activity had none, or a hand-off of
the one focus from the Activity. Any other change of the Activity's
focus ends that, so a notification shade still pauses as before.

The rule is a header without NDK types; a host test replays the
sequences the AYN Thor's focus modes and a one-focus system produce.

* android: keys and sticks from the second display's window

Keys and joystick moves the second display's window receives cross
from the UI thread in a ring of their fields; the input poll handles
them with the input queue's own code, so mapping, device ids, axes,
hats and hotplug behave the same. The window's focus comes in as two
commands, and the focus of both windows is acted on once per poll by
the focus rule, only when it changes.

A paused app thread sleeps in its looper, so writing to either of the
second display's rings wakes it, and the poll reads both rings after
the looper so the woken poll acts on what woke it. The app thread
keeps a reference on its looper for those wakes until the activity is
freed.

* android: the second display's window takes focus and passes keys on

The second display's Presentation can take focus, so a tap on it or a
focus mode that locks the gamepad to its display (the AYN Thor's Bottom)
gives it the pad. Its keys and joystick moves go to native input as the
Activity's do. Each key gets native code's answer, so Android makes no
fallback key (BUTTON_B as BACK, BUTTON_MODE as HOME) of a key RetroArch
uses. It is not cancelable, so BACK stays with native code, and it never
becomes the soft keyboard's target. The IME refuses the Activity's text
field while the second display's window holds the focus, so the Activity
asks for the soft keyboard again when it regains focus.

When RetroArch dismisses the Presentation while it holds the focus, it
first brings its own task to the front, which takes REORDER_TASKS
(granted at install). Android keeps one focus for all displays, so it
would otherwise pass to that display's launcher and leave RetroArch
without focus until the next tap on the main display.

* android: hand the second display's surface back without waiting

surfaceDestroyed and a window change on the second display return at
once. The renderer's own ANativeWindow references keep the window
alive; a frame still drawing into it gets an error from the abandoned
queue, and the next frame's poll finds the surface gone, drops it and
lets the EGL surface or Vulkan output go, the output on later frames
while the presentation engine still holds an image. Nothing waits on
the video thread or the GPU, so the UI thread can't stall into an ANR
behind a slow frame, and the window keeps its display for the next
surface. The going surface also gives the windows another try, so a
failure it caused is not held against the next.

vulkan_output_finish() is the driver's teardown only. An acquire it
gave up on keeps its fence, which is never destroyed while it may still
be signalled.

* video: build the screens' own windows only where a platform has them

HAVE_VIDEO_WINDOWS gates the secondary windows: their state in the
video driver and the threaded wrapper's slots, the drivers' window code
and its per-frame calls, the second output and EGL window surfaces,
the input layer's second pointer list and window places, and the
per-screen settings with their menu rows and config keys. Android
defines it; elsewhere ./configure --enable-video_windows does, and the
CI job that runs video_screens_test builds that way. A build without it
compiles none of this and carries none of its state, which is about
15 KB of the video driver's state alone.

* video: a window failure counts only for the restore it was drawn under

A renderer reports a window it could not make or keep with the restore
count of the frame it was drawing. Failures travel in one word, the
screens beside that count, and the main thread takes them only when
the count is still its own. Under threaded video a frame laid out
before a display or surface change can fail after that change; its
failure no longer keeps the screen on the main display until some
later event.

* android: the jelly-bean package may reorder tasks too

It builds the same second display controller, which brings RetroArch's
task back to the front when it dismisses the second display's window on
a one-focus system; without REORDER_TASKS that call throws and the focus
stays on that display's launcher.

---------

Co-authored-by: libretroadmin <[email protected]>
2026-10-24 10:41:49 +02:00
LibretroAdmin 21635dd171 samples: build with -g1 instead of -g
The sample and test builds used full debug info. -g1 keeps what
sanitizer reports and backtraces need - function names, files and line
numbers - and drops the variable and type information nobody reads in
CI. Objects get about 30% smaller (rh265.o under ASan+UBSan: 5.1MB to
3.7MB), so the ccache caches hold more of each job's build and are
quicker to restore and save.
2026-10-13 11:50:11 +02:00
LibretroAdmin 015298c2e3 input: a touch's presence is its own thing, not a position that is not 0
The pointer store took a touch to be there when its packed position
was not 0. A contact at the window's origin packs to 0, so a finger
down at 0,0 was not a touch: the pointer read the mouse and was not
pressed, and a lightgun aiming at the first touch aimed at the mouse.
DirectInput, raw input and SDL3 went by whether the contact existed
before they published, so for them this was a regression.

Where a contact is, whether there is one and whether it is down are
three things, and input_driver_publish_touches() takes them
separately: a position, a bit in @present, a bit in @down. A contact
at 0,0 is one; one that has lifted may keep its place, there and not
down - a finger's last place under Wayland, a pen in the air under
SDL3. DirectInput, raw input and SDL3's fingers say each contact they
list is there. Wayland keeps the rule it had, which is its own and is
now written where it belongs: a place at 0,0 is how the desktop reports
a mouse button, and is not a touch.

pointer_frame_test: a touch down at the origin, one lifted there, a
place with none, the viewport's edges, sparse places, the mouse's
fallback, the lightgun's choice, a pen in the air at the origin.
2026-10-06 09:48:49 +02:00
LibretroAdmin 991b1b996a input: drivers that place their own touches use the poll's viewport
The drivers the frontend does not answer for place a touch or a mouse
in the viewport themselves, and each asked the video driver for the
viewport every time: GameCube/Wii, PS3, PSP and the web on every read
of a pointer or lightgun value; Cocoa twice a touch a poll and once a
lightgun read; Switch once a touch a poll; Android and QNX once or
twice a touch event.

The frontend already keeps the viewport once a poll for the drivers
that publish. input_driver_poll_viewport() gives it to the others, with
two macros that take the place of video_driver_translate_coord_
viewport_wrap() and its confined twin, and those eight drivers use
them: one viewport a poll, however many touches and reads. How each
places its touches is unchanged. On the frontend's thread only, which
is where all eight do it; UWP, which may not, is left as it was.

pointer_frame_test checks the viewport is asked for once a poll and
afresh at the next.
2026-10-06 07:43:44 +02:00
LibretroAdmin 56b7172dee input: the SDL3 driver publishes its mouse, fingers and pen
SDL3 hands the frontend the mouse's frame, the fingers that are down
and the pen once a poll, and its own answers for the mouse, the pointer
and the lightgun's aim are deleted. Fingers are read before the pen and
the pen before the mouse, as before.

What the store learned for it:
- how many pointers are down (RETRO_DEVICE_ID_POINTER_COUNT): the
  touches that are, else the mouse standing for one. SDL3 answered
  this; the drivers converted before it gave 0, and now give the
  count too;
- a pointer placed as the lightgun is, -0x8000 outside the viewport,
  where the other drivers hold it to the edge
  (INPUT_POINTERS_POINTER_OFFSCREEN);
- a touch read alone (INPUT_POINTERS_TOUCH_ALONE): a pen in the air is
  a pointer that is not pressed, whatever the mouse's buttons say.
A touch is placed either way once a poll, as a mouse is.

pointer_frame_test covers the count and both flags.
2026-10-06 07:24:29 +02:00
LibretroAdmin 1c2345f656 input: DirectInput publishes its mouse and touches
The DirectInput driver hands the frontend its mouse's frame and its
touches, in the order they went down, once a poll, and its own answers
for the mouse, the pointer and the lightgun's aim are deleted.

Two things DirectInput does that the store now does for it, each a
flag a driver sets:
- the mouse is the port's whose Mouse Index is 0, while the pointer and
  the lightgun's aim are every port's (INPUT_POINTERS_AIM_EVERY_PORT);
- the lightgun aims where the first touch is while there is one
  (INPUT_POINTERS_GUN_AT_TOUCH), placed once a poll.

A wheel notch was kept until something read it, and the first read
used it up. It is the frame's now, as with the other drivers: cleared
at the next poll, and the same for every reader, a control bound to
the wheel included.

pointer_frame_test covers both flags.
2026-10-06 06:12:18 +02:00
LibretroAdmin 979465594f input: raw input publishes its mice and touches; ports by Mouse Index
The Windows raw input driver hands the frontend each mouse's frame and
its touches once a poll, and its own answers for the mouse, the pointer
and the lightgun's aim are deleted. Read by a core over a session of
moves, clicks and wheel turns under Wine it gives the same values as
before, line for line.

Three things the store needed for it:
- a port reads the mouse at its Mouse Index, or where its pinned mouse
  is (INPUT_POINTERS_BY_MOUSE_INDEX): which mouse a port reads is
  decided in the frontend, once, and no longer looked up by the driver
  on every value;
- a mouse standing for one touch, its left button, as raw input had it.
  The third argument of input_driver_publish_pointers() is flags now:
  that, or three touches as X11 and Wayland have it;
- raw input takes a mouse's reports differently when it is read as a
  pointer or as a lightgun. The frontend records what each published
  device was last read as, and the driver asks at its poll
  (input_driver_pointer_read_as()).
A port with no mouse still reads a real touch.

pointer_frame_test covers reading by Mouse Index, a port with no mouse,
and the record of what a device was read as. The raw input samples get
stand-ins for the three calls.
2026-10-06 05:43:56 +02:00
LibretroAdmin 59fa38d1b4 input: wayland publishes its mouse and touches; the store has touches
The Wayland driver hands the frontend its mouse's frame and its touches
once a poll, and its own answers for the mouse, the pointer and the
lightgun's aim are deleted; the lightgun's buttons, which are the
mouse's, stay. Read by a core over a session of moves, clicks and wheel
turns under Weston it gives the same values as before, but for a second
read of the wheel in a frame. With that core reading the pointer and
the lightgun's aim every frame, 300 frames took 2796 translations and
2798 viewport fetches before and take 600 and 302 now.

Three things change with it on Wayland. Relative motion adds up between
polls: each event used to replace the last, so only the last of a frame
counted. The motion and the wheel are taken once a poll, where the
first read cleared them. And with a real touch in a place, whether it
is off-screen goes by the touch, not by the mouse.

The frontend's store takes touches (input_driver_publish_touches()): a
place with a touch reads the touch, a place with none the mouse standing
for it. How many touches a mouse stands for is the driver's to say - 3
for X11 and Wayland, as they had it, 1 for the drivers that will take
the left button only - so input_driver_publish_pointers() gains that
argument.

The store moves to input/input_pointer_frame.h so that a test can drive
it: samples/input/pointer_frame, in the input samples job, counts the
viewport fetches and translations - eight pointer reads are one of each
- and covers touches, both ways a mouse stands for them, and the wheel.
2026-10-06 05:01:15 +02:00