mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-01 03:57:17 +01:00
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:
1 parent
56c76ace35
commit
d491fc90cf
17 files changed
+323
-8
No files matched your search
@@ -177,12 +177,78 @@ void ScreenManager::switchToNext() {
|
||||
void ScreenManager::deviceLost() {
|
||||
for (auto &iter : stack_)
|
||||
iter.screen->deviceLost();
|
||||
for (Output &output : outputs_) {
|
||||
if (output.display.swapchain) {
|
||||
draw_->DestroySwapchain(output.display.swapchain);
|
||||
output.display.swapchain = nullptr;
|
||||
}
|
||||
}
|
||||
// Outputs added before the device is restored get their swapchains then.
|
||||
deviceLost_ = true;
|
||||
}
|
||||
|
||||
void ScreenManager::deviceRestored(Draw::DrawContext *draw) {
|
||||
draw_ = draw;
|
||||
deviceLost_ = false;
|
||||
for (auto &iter : stack_)
|
||||
iter.screen->deviceRestored(draw);
|
||||
for (Output &output : outputs_) {
|
||||
std::string error;
|
||||
output.display.swapchain = draw_->CreateSwapchain(output.desc, &error);
|
||||
if (!output.display.swapchain) {
|
||||
ERROR_LOG(Log::G3D, "Failed to recreate the swapchain for output %d: %s", output.display.id, error.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenManager::AddOutput(int id, OutputRole role, const Draw::SwapchainDesc &desc) {
|
||||
Output output{};
|
||||
output.display.id = id;
|
||||
output.display.role = role;
|
||||
output.desc = desc;
|
||||
if (draw_ && !deviceLost_) {
|
||||
std::string error;
|
||||
output.display.swapchain = draw_->CreateSwapchain(desc, &error);
|
||||
if (!output.display.swapchain) {
|
||||
ERROR_LOG(Log::G3D, "Failed to create the swapchain for output %d: %s", id, error.c_str());
|
||||
}
|
||||
}
|
||||
INFO_LOG(Log::G3D, "Added output %d (%dx%d)", id, desc.width, desc.height);
|
||||
outputs_.push_back(output);
|
||||
}
|
||||
|
||||
void ScreenManager::ResizeOutput(int id, int width, int height) {
|
||||
for (Output &output : outputs_) {
|
||||
if (output.display.id == id) {
|
||||
output.desc.width = width;
|
||||
output.desc.height = height;
|
||||
if (output.display.swapchain) {
|
||||
draw_->ResizeSwapchain(output.display.swapchain, width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenManager::RemoveOutput(int id) {
|
||||
for (auto iter = outputs_.begin(); iter != outputs_.end(); ++iter) {
|
||||
if (iter->display.id == id) {
|
||||
if (iter->display.swapchain) {
|
||||
draw_->DestroySwapchain(iter->display.swapchain);
|
||||
}
|
||||
outputs_.erase(iter);
|
||||
INFO_LOG(Log::G3D, "Removed output %d", id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const OutputDisplay *ScreenManager::FindOutput(OutputRole role) const {
|
||||
for (const Output &output : outputs_) {
|
||||
if (output.display.role == role && output.display.swapchain) {
|
||||
return &output.display;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ScreenManager::resized() {
|
||||
@@ -212,6 +278,24 @@ ScreenRenderFlags ScreenManager::Render(std::function<void()> afterPreRender) {
|
||||
|
||||
afterPreRender();
|
||||
|
||||
// Secondary outputs. The topmost screen with something for one draws it, otherwise it's left cleared.
|
||||
for (const Output &output : outputs_) {
|
||||
Draw::Swapchain *swapchain = output.display.swapchain;
|
||||
if (!swapchain) {
|
||||
continue;
|
||||
}
|
||||
draw_->BindFramebufferAsRenderTarget(swapchain, {RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR}, "Output");
|
||||
int w, h;
|
||||
draw_->GetFramebufferDimensions(swapchain, &w, &h);
|
||||
draw_->SetViewport(Draw::Viewport{0.0f, 0.0f, (float)w, (float)h, 0.0f, 1.0f});
|
||||
draw_->SetScissorRect(0, 0, w, h);
|
||||
for (auto iter = stack_.rbegin(); iter != stack_.rend(); ++iter) {
|
||||
if (iter->screen->renderOutput(output.display)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now, start the final render pass. This is now the ONLY place where binding the backbuffer is allowed.
|
||||
draw_->BindFramebufferAsRenderTarget(draw_->GetPrimarySwapchain(), {RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR}, "BackBuffer");
|
||||
getUIContext()->BeginFrame();
|
||||
|
||||
Reference in new issue
Block a user