The screenshot readback ran in ScreenManager's post-render callback, and
on Vulkan it ends the backbuffer render step. renderImDebugger() draws
after that, so taking a screenshot with the debugger open made its first
SetViewport() write through a null render step (an assert in debug
builds, a Play crash report in release). Do the readback at the end of
the frame in NativeFrame instead, which also puts the debugger in the
screenshot, and remove the now unused callback.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The ScreenManager keeps a list of secondary outputs, each with its own
swapchain, recreated after a lost device. Platform code adds, resizes and
removes them from any thread through Native_AddOutput() and friends, which
take effect at the start of the next frame.
Each frame, every output is drawn by the topmost screen that claims it
(Screen::renderOutput). EmuScreen claims Game outputs: it presents the
game there, laid out for that output, and leaves it out of the backbuffer,
where the touch controls and the menus stay. Not in non-buffered mode,
which draws straight into the backbuffer.
--game-window opens such an output on Mac, with Metal. It's a smoke test
for multiple swapchains, and a stand-in for an AirPlay display.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
It only replaced the top screen, but most callers switch from a dialog:
Play on the game screen, the store, the installers, the framedump
launcher. The screens under it stayed alive, so launching a game from the
game screen and exiting it left [MainScreen, MainScreen], one more each
time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Popups are transparent, so the screen under one still renders, and
render() ran DoRecreateViews(). A RecreateViews() on that screen (a
resize or rotation on DisplayLayoutScreen or TouchControlLayoutScreen,
MainScreen on RECENT_FILES_CHANGED from the recent-files thread, the
achievements login finishing) then deleted the choice that opened the
popup. Closing the popup wrote through it and called its handlers.
Leave the old views in place until the screen is back on top, where
update() recreates them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NativeIsAtTopLevel() made a virtual call on the top screen from the Java
UI thread, while the render thread could be deleting it. Two quick Back
presses could hit that. Cache the flag in ScreenManager::Update()
instead, like passInputToMapper_ (now atomic too).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>