mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-31 11:37:00 +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
@@ -110,6 +110,21 @@ bool Native_IsWindowHidden();
|
||||
// TODO: Feels like this belongs elsewhere.
|
||||
bool Native_UpdateScreenScale(int width, int height, float customScale);
|
||||
|
||||
namespace Draw {
|
||||
struct SwapchainDesc;
|
||||
}
|
||||
|
||||
enum class OutputRole {
|
||||
Game, // The emulated display, with no UI.
|
||||
};
|
||||
|
||||
// Secondary outputs, like a second window or a TV (see ScreenManager::AddOutput). Safe to call from any
|
||||
// thread. They take effect at the start of the next frame. The surface in desc has to stay valid until
|
||||
// the output is removed, and that frame has started.
|
||||
int Native_AddOutput(OutputRole role, const Draw::SwapchainDesc &desc);
|
||||
void Native_ResizeOutput(int id, int width, int height);
|
||||
void Native_RemoveOutput(int id);
|
||||
|
||||
AudioBackend *System_CreateAudioBackend();
|
||||
|
||||
class NativeApplication : public Application {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "Common/Common.h"
|
||||
#include "Common/Input/InputState.h"
|
||||
#include "Common/System/NativeApp.h"
|
||||
#include "Common/System/System.h"
|
||||
|
||||
namespace UI {
|
||||
@@ -26,8 +27,16 @@ namespace UI {
|
||||
|
||||
namespace Draw {
|
||||
class DrawContext;
|
||||
class Swapchain;
|
||||
}
|
||||
|
||||
// A display other than the primary one, like a second window or a TV. See docs/multi-swapchain.md.
|
||||
struct OutputDisplay {
|
||||
int id;
|
||||
OutputRole role;
|
||||
Draw::Swapchain *swapchain; // nullptr while there's no draw context.
|
||||
};
|
||||
|
||||
enum DialogResult {
|
||||
DR_NONE,
|
||||
DR_OK,
|
||||
@@ -107,6 +116,9 @@ public:
|
||||
virtual void update() {}
|
||||
virtual ScreenRenderFlags PreRender(ScreenRenderMode mode) { return ScreenRenderFlags::NONE; }
|
||||
virtual ScreenRenderFlags render(ScreenRenderMode mode) = 0;
|
||||
// Draws this screen's content for a secondary output, whose swapchain is bound. Returns false if the
|
||||
// screen has nothing for it. The topmost screen that returns true is the one shown.
|
||||
virtual bool renderOutput(const OutputDisplay &output) { return false; }
|
||||
virtual void resized() {}
|
||||
virtual void dialogFinished(const Screen *dialog, DialogResult result) {}
|
||||
virtual void sendMessage(UIMessage message, const char *value) {}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "Common/Common.h"
|
||||
#include "Common/GPU/thin3d.h"
|
||||
#include "Common/Input/InputState.h"
|
||||
#include "Common/System/System.h"
|
||||
#include "Common/UI/Screen.h"
|
||||
@@ -93,6 +94,14 @@ public:
|
||||
return topIsTopLevel_;
|
||||
}
|
||||
|
||||
// Secondary outputs. Call these on the render thread, between frames. The swapchains are recreated
|
||||
// after a lost device, so the surface in desc has to stay valid until RemoveOutput.
|
||||
void AddOutput(int id, OutputRole role, const Draw::SwapchainDesc &desc);
|
||||
void ResizeOutput(int id, int width, int height);
|
||||
void RemoveOutput(int id);
|
||||
// nullptr if there's no output with the role, or it has no swapchain right now.
|
||||
const OutputDisplay *FindOutput(OutputRole role) const;
|
||||
|
||||
private:
|
||||
void pop();
|
||||
void switchToNext();
|
||||
@@ -125,6 +134,13 @@ private:
|
||||
|
||||
std::unordered_map<int64_t, int> lastAxis_;
|
||||
|
||||
struct Output {
|
||||
OutputDisplay display;
|
||||
Draw::SwapchainDesc desc;
|
||||
};
|
||||
std::vector<Output> outputs_;
|
||||
bool deviceLost_ = false;
|
||||
|
||||
std::atomic<InputMode> passInputToMapper_{InputMode::None};
|
||||
std::atomic<bool> topIsTopLevel_{false};
|
||||
};
|
||||
@@ -189,6 +189,7 @@ static const CommandLineParam g_autoParams[] = {
|
||||
{POFF(fullscreen), CmdParamType::BoolInverse, "windowed", '\0', "Force windowed mode", CmdLineMode::Application},
|
||||
{POFF(startScreen), CmdParamType::String, "start-screen", '\0', "Start on a specific screen (e.g. 'gamesettings', 'touchscreentest')", CmdLineMode::Application},
|
||||
{POFF(escapeExit), CmdParamType::Bool, "escape-exit", '\0', "Escape key exits the application", CmdLineMode::Application},
|
||||
{POFF(gameWindow), CmdParamType::Bool, "game-window", '\0', "Show the game in a second window, for testing multiple outputs (SDL, Metal only)", CmdLineMode::Application},
|
||||
{POFF(pauseMenuExit), CmdParamType::Bool, "pause-menu-exit", '\0', "Exit the application when the game ends, instead of returning to the menu", CmdLineMode::Application},
|
||||
{POFF(appendConfig), CmdParamType::String, "appendconfig", '\0', "Merge config FILE into the current configuration"},
|
||||
{POFF(root), CmdParamType::String, "root", 'r', "Mount directory as the root of host0:/."},
|
||||
|
||||
@@ -45,6 +45,7 @@ struct CommandLineOptions {
|
||||
std::optional<std::string> startScreen;
|
||||
|
||||
std::optional<bool> escapeExit;
|
||||
std::optional<bool> gameWindow;
|
||||
// Run as if no memory stick were inserted.
|
||||
std::optional<bool> memStickInserted;
|
||||
// Quit the app instead of going to the main menu when the game ends, for external launchers.
|
||||
|
||||
@@ -128,6 +128,10 @@ bool FramebufferManagerCommon::PresentedThisFrame() const {
|
||||
return presentation_->PresentedThisFrame();
|
||||
}
|
||||
|
||||
void FramebufferManagerCommon::SetPresentationOutput(const OutputParams *output) {
|
||||
presentation_->SetOutputOverride(output);
|
||||
}
|
||||
|
||||
void FramebufferManagerCommon::SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) {
|
||||
displayFramebufPtr_ = framebuf & 0x3FFFFFFF;
|
||||
if (Memory::IsVRAMAddress(displayFramebufPtr_))
|
||||
|
||||
@@ -295,6 +295,7 @@ struct DrawPixelsEntry {
|
||||
struct GPUDebugBuffer;
|
||||
class DrawEngineCommon;
|
||||
class PresentationCommon;
|
||||
struct OutputParams;
|
||||
class ShaderManagerCommon;
|
||||
class TextureCacheCommon;
|
||||
struct DisplayLayoutConfig;
|
||||
@@ -523,6 +524,7 @@ public:
|
||||
}
|
||||
|
||||
bool PresentedThisFrame() const;
|
||||
void SetPresentationOutput(const OutputParams *output);
|
||||
|
||||
const std::vector<VirtualFramebuffer *> &GetVFBs() const {
|
||||
return vfbs_;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
class FramebufferManagerCommon;
|
||||
class TextureCacheCommon;
|
||||
struct OutputParams;
|
||||
class DrawEngineCommon;
|
||||
class VertexDecoder;
|
||||
class GraphicsContext;
|
||||
@@ -152,6 +153,9 @@ public:
|
||||
virtual void PrepareCopyDisplayToOutput(const DisplayLayoutConfig &config) = 0;
|
||||
virtual void CopyDisplayToOutput(const DisplayLayoutConfig &config) = 0;
|
||||
virtual bool PresentedThisFrame() const = 0;
|
||||
// Presents the display to another output than the primary one, until called with nullptr. Set it
|
||||
// before PrepareCopyDisplayToOutput, which lays out for it.
|
||||
virtual void SetPresentationOutput(const OutputParams *output) {}
|
||||
|
||||
// Invalidate any cached content sourced from the specified range.
|
||||
// If size = -1, invalidate everything.
|
||||
|
||||
@@ -521,6 +521,10 @@ bool GPUCommonHW::PresentedThisFrame() const {
|
||||
return framebufferManager_->PresentedThisFrame();
|
||||
}
|
||||
|
||||
void GPUCommonHW::SetPresentationOutput(const OutputParams *output) {
|
||||
framebufferManager_->SetPresentationOutput(output);
|
||||
}
|
||||
|
||||
void GPUCommonHW::DoState(PointerWrap &p) {
|
||||
GPUCommon::DoState(p);
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ public:
|
||||
void ExecuteOp(u32 op, u32 diff) override;
|
||||
|
||||
bool PresentedThisFrame() const override;
|
||||
void SetPresentationOutput(const OutputParams *output) override;
|
||||
|
||||
private:
|
||||
void CheckDepthUsage(VirtualFramebuffer *vfb) override;
|
||||
|
||||
@@ -720,6 +720,12 @@ bool SoftGPU::PresentedThisFrame() const {
|
||||
return presentation_ ? presentation_->PresentedThisFrame() : false;
|
||||
}
|
||||
|
||||
void SoftGPU::SetPresentationOutput(const OutputParams *output) {
|
||||
if (presentation_) {
|
||||
presentation_->SetOutputOverride(output);
|
||||
}
|
||||
}
|
||||
|
||||
void SoftGPU::MarkDirty(uint32_t addr, uint32_t stride, uint32_t height, GEBufferFormat fmt, SoftGPUVRAMDirty value) {
|
||||
uint32_t bytes = height * stride * (fmt == GE_FORMAT_8888 ? 4 : 2);
|
||||
MarkDirty(addr, bytes, value);
|
||||
|
||||
@@ -256,6 +256,7 @@ public:
|
||||
|
||||
void BeginHostFrame(const DisplayLayoutConfig &config) override;
|
||||
bool PresentedThisFrame() const override;
|
||||
void SetPresentationOutput(const OutputParams *output) override;
|
||||
|
||||
protected:
|
||||
void FastRunLoop(DisplayList &list) override;
|
||||
|
||||
@@ -28,6 +28,7 @@ SDLJoystick *joystick = NULL;
|
||||
#include "Common/System/System.h"
|
||||
#include "Common/System/Request.h"
|
||||
#include "Common/System/NativeApp.h"
|
||||
#include "Common/GPU/thin3d.h"
|
||||
#include "Common/Audio/AudioBackend.h"
|
||||
#include "Core/CmdLine.h"
|
||||
#include "Core/EmuThread.h"
|
||||
@@ -1437,7 +1438,64 @@ void UpdateCursor() {
|
||||
SDL_SetCursor(g_builtinCursors[(int)curCursor]);
|
||||
}
|
||||
|
||||
// --game-window: a second window showing just the game, for testing multiple outputs.
|
||||
static SDL_Window *g_gameWindow = nullptr;
|
||||
static int g_gameOutputId = 0;
|
||||
|
||||
static void CreateGameWindow() {
|
||||
#if PPSSPP_PLATFORM(MAC)
|
||||
if (g_Config.iGPUBackend != (int)GPUBackend::METAL) {
|
||||
fprintf(stderr, "--game-window needs --graphics=metal\n");
|
||||
return;
|
||||
}
|
||||
g_gameWindow = SDL_CreateWindow("PPSSPP game", 960, 544, SDL_WINDOW_METAL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
|
||||
if (!g_gameWindow) {
|
||||
fprintf(stderr, "Error creating the game window: %s\n", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
SDL_MetalView metalView = SDL_Metal_CreateView(g_gameWindow);
|
||||
int width = 0, height = 0;
|
||||
SDL_GetWindowSizeInPixels(g_gameWindow, &width, &height);
|
||||
Draw::SwapchainDesc desc{};
|
||||
desc.winsys = WINDOWSYSTEM_METAL_EXT;
|
||||
desc.data1 = SDL_Metal_GetLayer(metalView);
|
||||
desc.width = width;
|
||||
desc.height = height;
|
||||
desc.tag = "GameWindow";
|
||||
g_gameOutputId = Native_AddOutput(OutputRole::Game, desc);
|
||||
#else
|
||||
fprintf(stderr, "--game-window is only implemented on Mac (Metal)\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Returns true if the event was for the game window, and has been handled. Keys still go to the emulator.
|
||||
static bool ProcessGameWindowEvent(const SDL_Event &event) {
|
||||
if (!g_gameWindow || SDL_GetWindowFromEvent(&event) != g_gameWindow) {
|
||||
return false;
|
||||
}
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
||||
Native_ResizeOutput(g_gameOutputId, event.window.data1, event.window.data2);
|
||||
return true;
|
||||
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
|
||||
// The layer has to outlive the swapchain, so the window stays until we quit.
|
||||
Native_RemoveOutput(g_gameOutputId);
|
||||
SDL_HideWindow(g_gameWindow);
|
||||
return true;
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
case SDL_EVENT_KEY_UP:
|
||||
case SDL_EVENT_TEXT_INPUT:
|
||||
return false;
|
||||
default:
|
||||
// Window events, and the mouse, which the UI in the main window would get the coordinates wrong for.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static void ProcessSDLEvent(SDL_Window *window, const SDL_Event &event, InputStateTracker *inputTracker) {
|
||||
if (ProcessGameWindowEvent(event)) {
|
||||
return;
|
||||
}
|
||||
// SDL input uses window coordinates; convert to pixels before applying the UI scale.
|
||||
float inputPixelDensity = SDL_GetWindowPixelDensity(window);
|
||||
if (inputPixelDensity <= 0.0f) {
|
||||
@@ -2289,6 +2347,10 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
SDL_SetWindowTitle(window, (app_name_nice + " " + PPSSPP_GIT_VERSION).c_str());
|
||||
|
||||
if (cmdLineOptions.gameWindow.value_or(false)) {
|
||||
CreateGameWindow();
|
||||
}
|
||||
|
||||
char iconPath[PATH_MAX];
|
||||
#if defined(ASSETS_DIR)
|
||||
snprintf(iconPath, PATH_MAX, "%sui_images/icon.png", ASSETS_DIR);
|
||||
|
||||
+38
-8
@@ -1643,14 +1643,14 @@ ScreenRenderFlags EmuScreen::PreRender(ScreenRenderMode mode) {
|
||||
|
||||
using namespace Draw;
|
||||
skipBufferEffects_ = g_Config.bSkipBufferEffects;
|
||||
UpdateGameOutput();
|
||||
if (!skipBufferEffects_ && PSP_IsInited()) {
|
||||
if (ShouldRunEmulation(mode)) {
|
||||
// We need to run emulation here, and perform all the normal render passes.
|
||||
return RunEmulation(false);
|
||||
} else if (gpu && GPUIsBuffered()) {
|
||||
// Need to check for gpu here, we might be in reset.
|
||||
const DeviceOrientation orientation = GetDeviceOrientation();
|
||||
const DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(orientation);
|
||||
const DisplayLayoutConfig &displayLayoutConfig = GameLayoutConfig();
|
||||
// We run just the post shaders.
|
||||
gpu->PrepareCopyDisplayToOutput(displayLayoutConfig);
|
||||
// Screens on top like reporting might want to take screenshots of existing framebuffers.
|
||||
@@ -1660,6 +1660,38 @@ ScreenRenderFlags EmuScreen::PreRender(ScreenRenderMode mode) {
|
||||
return ScreenRenderFlags::NONE;
|
||||
}
|
||||
|
||||
// Sends the game to a Game output, if there is one. Not in non-buffered mode, which draws straight
|
||||
// into the backbuffer during emulation.
|
||||
void EmuScreen::UpdateGameOutput() {
|
||||
const OutputDisplay *output = screenManager()->FindOutput(OutputRole::Game);
|
||||
gameOnOutput_ = output && gpu && !skipBufferEffects_ && GPUIsBuffered();
|
||||
if (!gpu) {
|
||||
return;
|
||||
}
|
||||
if (!gameOnOutput_) {
|
||||
gpu->SetPresentationOutput(nullptr);
|
||||
return;
|
||||
}
|
||||
OutputParams params;
|
||||
params.pixelWidth = output->swapchain->Width();
|
||||
params.pixelHeight = output->swapchain->Height();
|
||||
params.orientation = params.pixelHeight > params.pixelWidth * 1.1f ? DeviceOrientation::Portrait : DeviceOrientation::Landscape;
|
||||
gameOutputOrientation_ = params.orientation;
|
||||
gpu->SetPresentationOutput(¶ms);
|
||||
}
|
||||
|
||||
const DisplayLayoutConfig &EmuScreen::GameLayoutConfig() const {
|
||||
return g_Config.GetDisplayLayoutConfig(gameOnOutput_ ? gameOutputOrientation_ : GetDeviceOrientation());
|
||||
}
|
||||
|
||||
bool EmuScreen::renderOutput(const OutputDisplay &output) {
|
||||
if (output.role != OutputRole::Game || !gameOnOutput_ || !PSP_IsInited()) {
|
||||
return false;
|
||||
}
|
||||
gpu->CopyDisplayToOutput(GameLayoutConfig());
|
||||
return true;
|
||||
}
|
||||
|
||||
ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) {
|
||||
// Moved from update, because we want it to be possible for booting to happen even when the screen
|
||||
// is in the background, like when choosing Reset from the pause menu.
|
||||
@@ -1677,13 +1709,12 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) {
|
||||
System_Notify(SystemNotification::KEEP_SCREEN_AWAKE);
|
||||
}
|
||||
|
||||
const DeviceOrientation orientation = GetDeviceOrientation();
|
||||
const DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(orientation);
|
||||
const DisplayLayoutConfig &displayLayoutConfig = GameLayoutConfig();
|
||||
// We might have a bad viewport after RunEmulation, reset.
|
||||
Viewport viewport{0.0f, 0.0f, (float)g_display.pixel_xres, (float)g_display.pixel_yres, 0.0f, 1.0f};
|
||||
|
||||
if (!skipBufferEffects_ && !ShouldRunEmulation(mode)) {
|
||||
if (gpu) {
|
||||
if (gpu && !gameOnOutput_) {
|
||||
gpu->CopyDisplayToOutput(displayLayoutConfig);
|
||||
}
|
||||
draw->SetViewport(viewport);
|
||||
@@ -1716,7 +1747,7 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) {
|
||||
const bool skipBufferEffects = skipBufferEffects_;
|
||||
|
||||
// Gotta copy the output at some point. Also this is where we take the screenshot if needed.
|
||||
if (gpu) {
|
||||
if (gpu && !gameOnOutput_) {
|
||||
gpu->CopyDisplayToOutput(displayLayoutConfig);
|
||||
}
|
||||
|
||||
@@ -1771,8 +1802,7 @@ ScreenRenderFlags EmuScreen::RunEmulation(bool skipBufferEffects) {
|
||||
|
||||
g_OSD.NudgeIngameNotifications();
|
||||
|
||||
const DeviceOrientation orientation = GetDeviceOrientation();
|
||||
const DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(orientation);
|
||||
const DisplayLayoutConfig &displayLayoutConfig = GameLayoutConfig();
|
||||
__DisplaySetDisplayLayoutConfig(displayLayoutConfig);
|
||||
|
||||
DrawContext *draw = screenManager()->getDrawContext();
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "UI/ImDebugger/ImCommand.h"
|
||||
|
||||
struct AxisInput;
|
||||
struct DisplayLayoutConfig;
|
||||
|
||||
class AsyncImageFileView;
|
||||
class ChatMenu;
|
||||
@@ -66,6 +67,7 @@ protected:
|
||||
void darken();
|
||||
void focusChanged(ScreenFocusChange focusChange) override;
|
||||
ScreenRenderFlags PreRender(ScreenRenderMode mode) override;
|
||||
bool renderOutput(const OutputDisplay &output) override;
|
||||
|
||||
// ControlListener implementations
|
||||
void OnVKey(VirtKey virtualKeyCode, bool down) override;
|
||||
@@ -79,6 +81,8 @@ protected:
|
||||
private:
|
||||
void CreateViews() override;
|
||||
ScreenRenderFlags RunEmulation(bool skipBufferEffects);
|
||||
void UpdateGameOutput();
|
||||
const DisplayLayoutConfig &GameLayoutConfig() const;
|
||||
void OnDevTools(UI::EventParams ¶ms);
|
||||
void OpenChat(bool focus);
|
||||
|
||||
@@ -153,6 +157,9 @@ private:
|
||||
bool autoLoadFailed_ = false; // to prevent repeat reloads
|
||||
bool readyToFinishBoot_ = false;
|
||||
bool skipBufferEffects_ = false; // cached state, fetched once per frame.
|
||||
// The game goes to a secondary output (OutputRole::Game) instead of the backbuffer. Per frame too.
|
||||
bool gameOnOutput_ = false;
|
||||
DeviceOrientation gameOutputOrientation_ = DeviceOrientation::Landscape;
|
||||
|
||||
uint32_t clearColor_ = 0;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in new issue
Block a user