Secondary outputs, and --game-window on Mac to show the game in a second window

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>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-22 18:42:53 +02:00
1 parent 56c76ace35
commit d491fc90cf
17 files changed
+323 -8

No files matched your search

+65
View File
@@ -190,6 +190,38 @@ std::string config_filename;
// that consumes it also sees those writes - with a plain bool it could see the flag first and keep stale bounds.
static std::atomic<bool> resized{false};
struct OutputRequest {
enum class Type {
ADD,
RESIZE,
REMOVE,
};
Type type;
int id;
OutputRole role;
Draw::SwapchainDesc desc;
int width;
int height;
};
static std::mutex g_outputRequestLock;
static std::vector<OutputRequest> g_outputRequests;
static std::atomic<int> g_nextOutputId{1};
static void ProcessOutputRequests() {
std::vector<OutputRequest> requests;
{
std::lock_guard<std::mutex> guard(g_outputRequestLock);
requests.swap(g_outputRequests);
}
for (const OutputRequest &request : requests) {
switch (request.type) {
case OutputRequest::Type::ADD: g_screenManager->AddOutput(request.id, request.role, request.desc); break;
case OutputRequest::Type::RESIZE: g_screenManager->ResizeOutput(request.id, request.width, request.height); break;
case OutputRequest::Type::REMOVE: g_screenManager->RemoveOutput(request.id); break;
}
}
}
static int renderCounter = 0;
struct PendingMessage {
@@ -940,6 +972,8 @@ bool NativeInitGraphics(GraphicsContext *graphicsContext) {
g_screenManager->setUIContext(uiContext);
g_screenManager->setPostRenderCallback(&CallbackPostRender, nullptr);
// An output removed while there was no device can't be recreated, its surface may be gone.
ProcessOutputRequests();
g_screenManager->deviceRestored(g_draw);
g_audioBackend = System_CreateAudioBackend();
@@ -1193,6 +1227,8 @@ void NativeFrame(GraphicsContext *graphicsContext) {
#endif
}
ProcessOutputRequests();
// NOTE: We must begin the frame before update, so we can do texture size queries and stuff in Measure etc.
Draw::DebugFlags debugFlags = Draw::DebugFlags::NONE;
if ((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::GPU_PROFILE)
@@ -2000,6 +2036,35 @@ static bool IsWindowSmall(int pixelWidth, int pixelHeight) {
return config.InternalRotationIsPortrait() ? (h < 480 + 80) : (w < 480 + 80);
}
int Native_AddOutput(OutputRole role, const Draw::SwapchainDesc &desc) {
OutputRequest request{};
request.type = OutputRequest::Type::ADD;
request.id = g_nextOutputId++;
request.role = role;
request.desc = desc;
std::lock_guard<std::mutex> guard(g_outputRequestLock);
g_outputRequests.push_back(request);
return request.id;
}
void Native_ResizeOutput(int id, int width, int height) {
OutputRequest request{};
request.type = OutputRequest::Type::RESIZE;
request.id = id;
request.width = width;
request.height = height;
std::lock_guard<std::mutex> guard(g_outputRequestLock);
g_outputRequests.push_back(request);
}
void Native_RemoveOutput(int id) {
OutputRequest request{};
request.type = OutputRequest::Type::REMOVE;
request.id = id;
std::lock_guard<std::mutex> guard(g_outputRequestLock);
g_outputRequests.push_back(request);
}
bool Native_UpdateScreenScale(int pixel_width, int pixel_height, float customScale) {
INFO_LOG(Log::System, "Native_UpdateScreenScale: %dx%d, customScale=%f", pixel_width, pixel_height, customScale);