Add "Separate Windows" LayoutOption (#6177)
This commit is contained in:
parent
4f715b6718
commit
f44c95d638
|
@ -35,6 +35,7 @@
|
||||||
#include "core/loader/loader.h"
|
#include "core/loader/loader.h"
|
||||||
#include "core/movie.h"
|
#include "core/movie.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
#include "input_common/main.h"
|
||||||
#include "network/network.h"
|
#include "network/network.h"
|
||||||
#include "video_core/renderer_base.h"
|
#include "video_core/renderer_base.h"
|
||||||
|
|
||||||
|
@ -359,11 +360,23 @@ int main(int argc, char** argv) {
|
||||||
// Register generic image interface
|
// Register generic image interface
|
||||||
Core::System::GetInstance().RegisterImageInterface(std::make_shared<LodePNGImageInterface>());
|
Core::System::GetInstance().RegisterImageInterface(std::make_shared<LodePNGImageInterface>());
|
||||||
|
|
||||||
std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen)};
|
EmuWindow_SDL2::InitializeSDL2();
|
||||||
Frontend::ScopeAcquireContext scope(*emu_window);
|
|
||||||
Core::System& system = Core::System::GetInstance();
|
|
||||||
|
|
||||||
const Core::System::ResultStatus load_result{system.Load(*emu_window, filepath)};
|
const auto emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen, false)};
|
||||||
|
const bool use_secondary_window{Settings::values.layout_option ==
|
||||||
|
Settings::LayoutOption::SeparateWindows};
|
||||||
|
const auto secondary_window =
|
||||||
|
use_secondary_window ? std::make_unique<EmuWindow_SDL2>(false, true) : nullptr;
|
||||||
|
|
||||||
|
Frontend::ScopeAcquireContext scope(*emu_window);
|
||||||
|
|
||||||
|
LOG_INFO(Frontend, "Citra Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
|
||||||
|
Common::g_scm_desc);
|
||||||
|
Settings::LogSettings();
|
||||||
|
|
||||||
|
Core::System& system = Core::System::GetInstance();
|
||||||
|
const Core::System::ResultStatus load_result{
|
||||||
|
system.Load(*emu_window, filepath, secondary_window.get())};
|
||||||
|
|
||||||
switch (load_result) {
|
switch (load_result) {
|
||||||
case Core::System::ResultStatus::ErrorGetLoader:
|
case Core::System::ResultStatus::ErrorGetLoader:
|
||||||
|
@ -431,7 +444,12 @@ int main(int argc, char** argv) {
|
||||||
system.VideoDumper().StartDumping(dump_video, layout);
|
system.VideoDumper().StartDumping(dump_video, layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::thread render_thread([&emu_window] { emu_window->Present(); });
|
std::thread main_render_thread([&emu_window] { emu_window->Present(); });
|
||||||
|
std::thread secondary_render_thread([&secondary_window] {
|
||||||
|
if (secondary_window) {
|
||||||
|
secondary_window->Present();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
std::atomic_bool stop_run;
|
std::atomic_bool stop_run;
|
||||||
system.Renderer().Rasterizer()->LoadDiskResources(
|
system.Renderer().Rasterizer()->LoadDiskResources(
|
||||||
|
@ -440,7 +458,11 @@ int main(int argc, char** argv) {
|
||||||
total);
|
total);
|
||||||
});
|
});
|
||||||
|
|
||||||
while (emu_window->IsOpen()) {
|
const auto secondary_is_open = [&secondary_window] {
|
||||||
|
// if the secondary window isn't created, it shouldn't affect the main loop
|
||||||
|
return secondary_window ? secondary_window->IsOpen() : true;
|
||||||
|
};
|
||||||
|
while (emu_window->IsOpen() && secondary_is_open()) {
|
||||||
const auto result = system.RunLoop();
|
const auto result = system.RunLoop();
|
||||||
|
|
||||||
switch (result) {
|
switch (result) {
|
||||||
|
@ -454,13 +476,21 @@ int main(int argc, char** argv) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
render_thread.join();
|
emu_window->RequestClose();
|
||||||
|
if (secondary_window) {
|
||||||
|
secondary_window->RequestClose();
|
||||||
|
}
|
||||||
|
main_render_thread.join();
|
||||||
|
secondary_render_thread.join();
|
||||||
|
|
||||||
Core::Movie::GetInstance().Shutdown();
|
Core::Movie::GetInstance().Shutdown();
|
||||||
if (system.VideoDumper().IsDumping()) {
|
if (system.VideoDumper().IsDumping()) {
|
||||||
system.VideoDumper().StopDumping();
|
system.VideoDumper().StopDumping();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Network::Shutdown();
|
||||||
|
InputCommon::Shutdown();
|
||||||
|
|
||||||
system.Shutdown();
|
system.Shutdown();
|
||||||
|
|
||||||
detached_tasks.WaitForAllTasks();
|
detached_tasks.WaitForAllTasks();
|
||||||
|
|
|
@ -182,7 +182,11 @@ filter_mode =
|
||||||
|
|
||||||
[Layout]
|
[Layout]
|
||||||
# Layout for the screen inside the render window.
|
# Layout for the screen inside the render window.
|
||||||
# 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen, 3: Side by Side
|
# 0 (default): Default Top Bottom Screen
|
||||||
|
# 1: Single Screen Only
|
||||||
|
# 2: Large Screen Small Screen
|
||||||
|
# 3: Side by Side
|
||||||
|
# 4: Separate Windows
|
||||||
layout_option =
|
layout_option =
|
||||||
|
|
||||||
# Toggle custom layout (using the settings below) on or off.
|
# Toggle custom layout (using the settings below) on or off.
|
||||||
|
|
|
@ -135,18 +135,8 @@ void EmuWindow_SDL2::Fullscreen() {
|
||||||
SDL_MaximizeWindow(render_window);
|
SDL_MaximizeWindow(render_window);
|
||||||
}
|
}
|
||||||
|
|
||||||
EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen, bool is_secondary) : EmuWindow(is_secondary) {
|
||||||
// Initialize the window
|
// Initialize the window
|
||||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) < 0) {
|
|
||||||
LOG_CRITICAL(Frontend, "Failed to initialize SDL2: {}! Exiting...", SDL_GetError());
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
InputCommon::Init();
|
|
||||||
Network::Init();
|
|
||||||
|
|
||||||
SDL_SetMainReady();
|
|
||||||
|
|
||||||
if (Settings::values.use_gles) {
|
if (Settings::values.use_gles) {
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
||||||
|
@ -201,6 +191,7 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render_window_id = SDL_GetWindowID(render_window);
|
||||||
auto gl_load_func = Settings::values.use_gles ? gladLoadGLES2Loader : gladLoadGLLoader;
|
auto gl_load_func = Settings::values.use_gles ? gladLoadGLES2Loader : gladLoadGLLoader;
|
||||||
|
|
||||||
if (!gl_load_func(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
|
if (!gl_load_func(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
|
||||||
|
@ -211,19 +202,26 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
||||||
OnResize();
|
OnResize();
|
||||||
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
|
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
|
||||||
SDL_PumpEvents();
|
SDL_PumpEvents();
|
||||||
LOG_INFO(Frontend, "Citra Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
|
|
||||||
Common::g_scm_desc);
|
|
||||||
Settings::LogSettings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EmuWindow_SDL2::~EmuWindow_SDL2() {
|
EmuWindow_SDL2::~EmuWindow_SDL2() {
|
||||||
core_context.reset();
|
core_context.reset();
|
||||||
Network::Shutdown();
|
|
||||||
InputCommon::Shutdown();
|
|
||||||
SDL_GL_DeleteContext(window_context);
|
SDL_GL_DeleteContext(window_context);
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EmuWindow_SDL2::InitializeSDL2() {
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) < 0) {
|
||||||
|
LOG_CRITICAL(Frontend, "Failed to initialize SDL2: {}! Exiting...", SDL_GetError());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputCommon::Init();
|
||||||
|
Network::Init();
|
||||||
|
|
||||||
|
SDL_SetMainReady();
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<Frontend::GraphicsContext> EmuWindow_SDL2::CreateSharedContext() const {
|
std::unique_ptr<Frontend::GraphicsContext> EmuWindow_SDL2::CreateSharedContext() const {
|
||||||
return std::make_unique<SharedContext_SDL2>();
|
return std::make_unique<SharedContext_SDL2>();
|
||||||
}
|
}
|
||||||
|
@ -240,7 +238,7 @@ void EmuWindow_SDL2::Present() {
|
||||||
SDL_GL_MakeCurrent(render_window, window_context);
|
SDL_GL_MakeCurrent(render_window, window_context);
|
||||||
SDL_GL_SetSwapInterval(1);
|
SDL_GL_SetSwapInterval(1);
|
||||||
while (IsOpen()) {
|
while (IsOpen()) {
|
||||||
VideoCore::g_renderer->TryPresent(100);
|
VideoCore::g_renderer->TryPresent(100, is_secondary);
|
||||||
SDL_GL_SwapWindow(render_window);
|
SDL_GL_SwapWindow(render_window);
|
||||||
}
|
}
|
||||||
SDL_GL_MakeCurrent(render_window, nullptr);
|
SDL_GL_MakeCurrent(render_window, nullptr);
|
||||||
|
@ -248,9 +246,14 @@ void EmuWindow_SDL2::Present() {
|
||||||
|
|
||||||
void EmuWindow_SDL2::PollEvents() {
|
void EmuWindow_SDL2::PollEvents() {
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
|
std::vector<SDL_Event> other_window_events;
|
||||||
|
|
||||||
// SDL_PollEvent returns 0 when there are no more events in the event queue
|
// SDL_PollEvent returns 0 when there are no more events in the event queue
|
||||||
while (SDL_PollEvent(&event)) {
|
while (SDL_PollEvent(&event)) {
|
||||||
|
if (event.window.windowID != render_window_id) {
|
||||||
|
other_window_events.push_back(event);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case SDL_WINDOWEVENT:
|
case SDL_WINDOWEVENT:
|
||||||
switch (event.window.event) {
|
switch (event.window.event) {
|
||||||
|
@ -299,16 +302,13 @@ void EmuWindow_SDL2::PollEvents() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (auto& e : other_window_events) {
|
||||||
const u32 current_time = SDL_GetTicks();
|
// This is a somewhat hacky workaround to re-emit window events meant for another window
|
||||||
if (current_time > last_time + 2000) {
|
// since SDL_PollEvent() is global but we poll events per window.
|
||||||
const auto results = Core::System::GetInstance().GetAndResetPerfStats();
|
SDL_PushEvent(&e);
|
||||||
const auto title =
|
}
|
||||||
fmt::format("Citra {} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname,
|
if (!is_secondary) {
|
||||||
Common::g_scm_branch, Common::g_scm_desc, results.game_fps,
|
UpdateFramerateCounter();
|
||||||
results.emulation_speed * 100.0f);
|
|
||||||
SDL_SetWindowTitle(render_window, title.c_str());
|
|
||||||
last_time = current_time;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,3 +323,16 @@ void EmuWindow_SDL2::DoneCurrent() {
|
||||||
void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
|
void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
|
||||||
SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);
|
SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EmuWindow_SDL2::UpdateFramerateCounter() {
|
||||||
|
const u32 current_time = SDL_GetTicks();
|
||||||
|
if (current_time > last_time + 2000) {
|
||||||
|
const auto results = Core::System::GetInstance().GetAndResetPerfStats();
|
||||||
|
const auto title =
|
||||||
|
fmt::format("Citra {} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname,
|
||||||
|
Common::g_scm_branch, Common::g_scm_desc, results.game_fps,
|
||||||
|
results.emulation_speed * 100.0f);
|
||||||
|
SDL_SetWindowTitle(render_window, title.c_str());
|
||||||
|
last_time = current_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -29,9 +29,11 @@ private:
|
||||||
|
|
||||||
class EmuWindow_SDL2 : public Frontend::EmuWindow {
|
class EmuWindow_SDL2 : public Frontend::EmuWindow {
|
||||||
public:
|
public:
|
||||||
explicit EmuWindow_SDL2(bool fullscreen);
|
explicit EmuWindow_SDL2(bool fullscreen, bool is_secondary);
|
||||||
~EmuWindow_SDL2();
|
~EmuWindow_SDL2();
|
||||||
|
|
||||||
|
static void InitializeSDL2();
|
||||||
|
|
||||||
void Present();
|
void Present();
|
||||||
|
|
||||||
/// Polls window events
|
/// Polls window events
|
||||||
|
@ -88,12 +90,18 @@ private:
|
||||||
/// Called when a configuration change affects the minimal size of the window
|
/// Called when a configuration change affects the minimal size of the window
|
||||||
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
|
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
|
||||||
|
|
||||||
|
/// Called when polling to update framerate
|
||||||
|
void UpdateFramerateCounter();
|
||||||
|
|
||||||
/// Is the window still open?
|
/// Is the window still open?
|
||||||
bool is_open = true;
|
bool is_open = true;
|
||||||
|
|
||||||
/// Internal SDL2 render window
|
/// Internal SDL2 render window
|
||||||
SDL_Window* render_window;
|
SDL_Window* render_window;
|
||||||
|
|
||||||
|
/// Internal SDL2 window ID
|
||||||
|
int render_window_id{};
|
||||||
|
|
||||||
/// Fake hidden window for the core context
|
/// Fake hidden window for the core context
|
||||||
SDL_Window* dummy_window;
|
SDL_Window* dummy_window;
|
||||||
|
|
||||||
|
|
|
@ -113,9 +113,10 @@ void EmuThread::run() {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenGLWindow::OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context)
|
OpenGLWindow::OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context,
|
||||||
|
bool is_secondary)
|
||||||
: QWindow(parent), context(std::make_unique<QOpenGLContext>(shared_context->parent())),
|
: QWindow(parent), context(std::make_unique<QOpenGLContext>(shared_context->parent())),
|
||||||
event_handler(event_handler) {
|
event_handler(event_handler), is_secondary{is_secondary} {
|
||||||
|
|
||||||
// disable vsync for any shared contexts
|
// disable vsync for any shared contexts
|
||||||
auto format = shared_context->format();
|
auto format = shared_context->format();
|
||||||
|
@ -143,7 +144,7 @@ void OpenGLWindow::Present() {
|
||||||
|
|
||||||
context->makeCurrent(this);
|
context->makeCurrent(this);
|
||||||
if (VideoCore::g_renderer) {
|
if (VideoCore::g_renderer) {
|
||||||
VideoCore::g_renderer->TryPresent(100);
|
VideoCore::g_renderer->TryPresent(100, is_secondary);
|
||||||
}
|
}
|
||||||
context->swapBuffers(this);
|
context->swapBuffers(this);
|
||||||
auto f = context->versionFunctions<QOpenGLFunctions_4_3_Core>();
|
auto f = context->versionFunctions<QOpenGLFunctions_4_3_Core>();
|
||||||
|
@ -196,8 +197,8 @@ void OpenGLWindow::exposeEvent(QExposeEvent* event) {
|
||||||
QWindow::exposeEvent(event);
|
QWindow::exposeEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread)
|
GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread, bool is_secondary_)
|
||||||
: QWidget(parent_), emu_thread(emu_thread) {
|
: QWidget(parent_), EmuWindow(is_secondary_), emu_thread(emu_thread) {
|
||||||
|
|
||||||
setWindowTitle(QStringLiteral("Citra %1 | %2-%3")
|
setWindowTitle(QStringLiteral("Citra %1 | %2-%3")
|
||||||
.arg(QString::fromUtf8(Common::g_build_name),
|
.arg(QString::fromUtf8(Common::g_build_name),
|
||||||
|
@ -207,7 +208,6 @@ GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread)
|
||||||
auto layout = new QHBoxLayout(this);
|
auto layout = new QHBoxLayout(this);
|
||||||
layout->setContentsMargins(0, 0, 0, 0);
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
setLayout(layout);
|
setLayout(layout);
|
||||||
InputCommon::Init();
|
|
||||||
|
|
||||||
this->setMouseTracking(true);
|
this->setMouseTracking(true);
|
||||||
|
|
||||||
|
@ -215,9 +215,7 @@ GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread)
|
||||||
connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
|
connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
|
||||||
}
|
}
|
||||||
|
|
||||||
GRenderWindow::~GRenderWindow() {
|
GRenderWindow::~GRenderWindow() = default;
|
||||||
InputCommon::Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GRenderWindow::MakeCurrent() {
|
void GRenderWindow::MakeCurrent() {
|
||||||
core_context->MakeCurrent();
|
core_context->MakeCurrent();
|
||||||
|
@ -382,6 +380,12 @@ bool GRenderWindow::event(QEvent* event) {
|
||||||
void GRenderWindow::focusOutEvent(QFocusEvent* event) {
|
void GRenderWindow::focusOutEvent(QFocusEvent* event) {
|
||||||
QWidget::focusOutEvent(event);
|
QWidget::focusOutEvent(event);
|
||||||
InputCommon::GetKeyboard()->ReleaseAllKeys();
|
InputCommon::GetKeyboard()->ReleaseAllKeys();
|
||||||
|
has_focus = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GRenderWindow::focusInEvent(QFocusEvent* event) {
|
||||||
|
QWidget::focusInEvent(event);
|
||||||
|
has_focus = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::resizeEvent(QResizeEvent* event) {
|
void GRenderWindow::resizeEvent(QResizeEvent* event) {
|
||||||
|
@ -396,7 +400,8 @@ void GRenderWindow::InitRenderTarget() {
|
||||||
|
|
||||||
GMainWindow* parent = GetMainWindow();
|
GMainWindow* parent = GetMainWindow();
|
||||||
QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr;
|
QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr;
|
||||||
child_window = new OpenGLWindow(parent_win_handle, this, QOpenGLContext::globalShareContext());
|
child_window = new OpenGLWindow(parent_win_handle, this, QOpenGLContext::globalShareContext(),
|
||||||
|
is_secondary);
|
||||||
child_window->create();
|
child_window->create();
|
||||||
child_widget = createWindowContainer(child_window, this);
|
child_widget = createWindowContainer(child_window, this);
|
||||||
child_widget->resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
|
child_widget->resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
|
||||||
|
@ -421,7 +426,7 @@ void GRenderWindow::ReleaseRenderTarget() {
|
||||||
void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
|
void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
|
||||||
if (res_scale == 0)
|
if (res_scale == 0)
|
||||||
res_scale = VideoCore::GetResolutionScaleFactor();
|
res_scale = VideoCore::GetResolutionScaleFactor();
|
||||||
const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)};
|
const auto layout{Layout::FrameLayoutFromResolutionScale(res_scale, is_secondary)};
|
||||||
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
|
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
|
||||||
VideoCore::RequestScreenshot(
|
VideoCore::RequestScreenshot(
|
||||||
screenshot_image.bits(),
|
screenshot_image.bits(),
|
||||||
|
|
|
@ -129,7 +129,8 @@ signals:
|
||||||
class OpenGLWindow : public QWindow {
|
class OpenGLWindow : public QWindow {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context);
|
explicit OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context,
|
||||||
|
bool is_secondary = false);
|
||||||
|
|
||||||
~OpenGLWindow();
|
~OpenGLWindow();
|
||||||
|
|
||||||
|
@ -142,13 +143,14 @@ protected:
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<QOpenGLContext> context;
|
std::unique_ptr<QOpenGLContext> context;
|
||||||
QWidget* event_handler;
|
QWidget* event_handler;
|
||||||
|
bool is_secondary;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GRenderWindow : public QWidget, public Frontend::EmuWindow {
|
class GRenderWindow : public QWidget, public Frontend::EmuWindow {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GRenderWindow(QWidget* parent, EmuThread* emu_thread);
|
GRenderWindow(QWidget* parent, EmuThread* emu_thread, bool is_secondary);
|
||||||
~GRenderWindow() override;
|
~GRenderWindow() override;
|
||||||
|
|
||||||
// EmuWindow implementation.
|
// EmuWindow implementation.
|
||||||
|
@ -178,6 +180,10 @@ public:
|
||||||
bool event(QEvent* event) override;
|
bool event(QEvent* event) override;
|
||||||
|
|
||||||
void focusOutEvent(QFocusEvent* event) override;
|
void focusOutEvent(QFocusEvent* event) override;
|
||||||
|
void focusInEvent(QFocusEvent* event) override;
|
||||||
|
bool HasFocus() const {
|
||||||
|
return has_focus;
|
||||||
|
}
|
||||||
|
|
||||||
void InitRenderTarget();
|
void InitRenderTarget();
|
||||||
|
|
||||||
|
@ -229,6 +235,7 @@ private:
|
||||||
/// Temporary storage of the screenshot taken
|
/// Temporary storage of the screenshot taken
|
||||||
QImage screenshot_image;
|
QImage screenshot_image;
|
||||||
bool first_frame = false;
|
bool first_frame = false;
|
||||||
|
bool has_focus = false;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void showEvent(QShowEvent* event) override;
|
void showEvent(QShowEvent* event) override;
|
||||||
|
|
|
@ -261,6 +261,11 @@
|
||||||
<string>Side by Side</string>
|
<string>Side by Side</string>
|
||||||
</property>
|
</property>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Separate Windows</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
|
|
@ -87,6 +87,7 @@
|
||||||
#include "core/savestate.h"
|
#include "core/savestate.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "game_list_p.h"
|
#include "game_list_p.h"
|
||||||
|
#include "input_common/main.h"
|
||||||
#include "network/network_settings.h"
|
#include "network/network_settings.h"
|
||||||
#include "ui_main.h"
|
#include "ui_main.h"
|
||||||
#include "video_core/renderer_base.h"
|
#include "video_core/renderer_base.h"
|
||||||
|
@ -256,8 +257,11 @@ void GMainWindow::InitializeWidgets() {
|
||||||
#ifdef CITRA_ENABLE_COMPATIBILITY_REPORTING
|
#ifdef CITRA_ENABLE_COMPATIBILITY_REPORTING
|
||||||
ui->action_Report_Compatibility->setVisible(true);
|
ui->action_Report_Compatibility->setVisible(true);
|
||||||
#endif
|
#endif
|
||||||
render_window = new GRenderWindow(this, emu_thread.get());
|
render_window = new GRenderWindow(this, emu_thread.get(), false);
|
||||||
|
secondary_window = new GRenderWindow(this, emu_thread.get(), true);
|
||||||
render_window->hide();
|
render_window->hide();
|
||||||
|
secondary_window->hide();
|
||||||
|
secondary_window->setParent(nullptr);
|
||||||
|
|
||||||
game_list = new GameList(this);
|
game_list = new GameList(this);
|
||||||
ui->horizontalLayout->addWidget(game_list);
|
ui->horizontalLayout->addWidget(game_list);
|
||||||
|
@ -277,6 +281,7 @@ void GMainWindow::InitializeWidgets() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
InputCommon::Init();
|
||||||
multiplayer_state = new MultiplayerState(this, game_list->GetModel(), ui->action_Leave_Room,
|
multiplayer_state = new MultiplayerState(this, game_list->GetModel(), ui->action_Leave_Room,
|
||||||
ui->action_Show_Room);
|
ui->action_Show_Room);
|
||||||
multiplayer_state->setVisible(false);
|
multiplayer_state->setVisible(false);
|
||||||
|
@ -327,6 +332,7 @@ void GMainWindow::InitializeWidgets() {
|
||||||
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Single_Screen);
|
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Single_Screen);
|
||||||
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Large_Screen);
|
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Large_Screen);
|
||||||
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Side_by_Side);
|
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Side_by_Side);
|
||||||
|
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Separate_Windows);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::InitializeDebugWidgets() {
|
void GMainWindow::InitializeDebugWidgets() {
|
||||||
|
@ -516,6 +522,17 @@ void GMainWindow::InitializeHotkeys() {
|
||||||
&QShortcut::activated, ui->action_Fullscreen, &QAction::trigger);
|
&QShortcut::activated, ui->action_Fullscreen, &QAction::trigger);
|
||||||
connect(hotkey_registry.GetHotkey(main_window, fullscreen, render_window),
|
connect(hotkey_registry.GetHotkey(main_window, fullscreen, render_window),
|
||||||
&QShortcut::activatedAmbiguously, ui->action_Fullscreen, &QAction::trigger);
|
&QShortcut::activatedAmbiguously, ui->action_Fullscreen, &QAction::trigger);
|
||||||
|
|
||||||
|
// This action will fire specifically when secondary_window is in focus
|
||||||
|
QAction* secondary_fullscreen_action = new QAction(secondary_window);
|
||||||
|
// Use the same fullscreen hotkey as the main window
|
||||||
|
const auto fullscreen_hotkey = hotkey_registry.GetKeySequence(main_window, fullscreen);
|
||||||
|
secondary_fullscreen_action->setShortcut(fullscreen_hotkey);
|
||||||
|
|
||||||
|
connect(secondary_fullscreen_action, SIGNAL(triggered()), this,
|
||||||
|
SLOT(ToggleSecondaryFullscreen()));
|
||||||
|
secondary_window->addAction(secondary_fullscreen_action);
|
||||||
|
|
||||||
connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Exit Fullscreen"), this),
|
connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Exit Fullscreen"), this),
|
||||||
&QShortcut::activated, this, [&] {
|
&QShortcut::activated, this, [&] {
|
||||||
if (emulation_running) {
|
if (emulation_running) {
|
||||||
|
@ -690,6 +707,10 @@ void GMainWindow::ConnectWidgetEvents() {
|
||||||
&GRenderWindow::OnEmulationStarting);
|
&GRenderWindow::OnEmulationStarting);
|
||||||
connect(this, &GMainWindow::EmulationStopping, render_window,
|
connect(this, &GMainWindow::EmulationStopping, render_window,
|
||||||
&GRenderWindow::OnEmulationStopping);
|
&GRenderWindow::OnEmulationStopping);
|
||||||
|
connect(this, &GMainWindow::EmulationStarting, secondary_window,
|
||||||
|
&GRenderWindow::OnEmulationStarting);
|
||||||
|
connect(this, &GMainWindow::EmulationStopping, secondary_window,
|
||||||
|
&GRenderWindow::OnEmulationStopping);
|
||||||
|
|
||||||
connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar);
|
connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar);
|
||||||
|
|
||||||
|
@ -763,6 +784,8 @@ void GMainWindow::ConnectMenuEvents() {
|
||||||
&GMainWindow::ChangeScreenLayout);
|
&GMainWindow::ChangeScreenLayout);
|
||||||
connect(ui->action_Screen_Layout_Side_by_Side, &QAction::triggered, this,
|
connect(ui->action_Screen_Layout_Side_by_Side, &QAction::triggered, this,
|
||||||
&GMainWindow::ChangeScreenLayout);
|
&GMainWindow::ChangeScreenLayout);
|
||||||
|
connect(ui->action_Screen_Layout_Separate_Windows, &QAction::triggered, this,
|
||||||
|
&GMainWindow::ChangeScreenLayout);
|
||||||
connect(ui->action_Screen_Layout_Swap_Screens, &QAction::triggered, this,
|
connect(ui->action_Screen_Layout_Swap_Screens, &QAction::triggered, this,
|
||||||
&GMainWindow::OnSwapScreens);
|
&GMainWindow::OnSwapScreens);
|
||||||
connect(ui->action_Screen_Layout_Upright_Screens, &QAction::triggered, this,
|
connect(ui->action_Screen_Layout_Upright_Screens, &QAction::triggered, this,
|
||||||
|
@ -922,6 +945,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||||
ShutdownGame();
|
ShutdownGame();
|
||||||
|
|
||||||
render_window->InitRenderTarget();
|
render_window->InitRenderTarget();
|
||||||
|
secondary_window->InitRenderTarget();
|
||||||
|
|
||||||
Frontend::ScopeAcquireContext scope(*render_window);
|
Frontend::ScopeAcquireContext scope(*render_window);
|
||||||
|
|
||||||
|
@ -936,7 +960,8 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||||
|
|
||||||
Core::System& system{Core::System::GetInstance()};
|
Core::System& system{Core::System::GetInstance()};
|
||||||
|
|
||||||
const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
|
const Core::System::ResultStatus result{
|
||||||
|
system.Load(*render_window, filename.toStdString(), secondary_window)};
|
||||||
|
|
||||||
if (result != Core::System::ResultStatus::Success) {
|
if (result != Core::System::ResultStatus::Success) {
|
||||||
switch (result) {
|
switch (result) {
|
||||||
|
@ -1098,6 +1123,8 @@ void GMainWindow::BootGame(const QString& filename) {
|
||||||
|
|
||||||
connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
|
connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
|
||||||
connect(render_window, &GRenderWindow::MouseActivity, this, &GMainWindow::OnMouseActivity);
|
connect(render_window, &GRenderWindow::MouseActivity, this, &GMainWindow::OnMouseActivity);
|
||||||
|
connect(secondary_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
|
||||||
|
connect(secondary_window, &GRenderWindow::MouseActivity, this, &GMainWindow::OnMouseActivity);
|
||||||
|
|
||||||
// BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views
|
// BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views
|
||||||
// before the CPU continues
|
// before the CPU continues
|
||||||
|
@ -1189,6 +1216,7 @@ void GMainWindow::ShutdownGame() {
|
||||||
|
|
||||||
// The emulation is stopped, so closing the window or not does not matter anymore
|
// The emulation is stopped, so closing the window or not does not matter anymore
|
||||||
disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
|
disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
|
||||||
|
disconnect(secondary_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
|
||||||
|
|
||||||
// Update the GUI
|
// Update the GUI
|
||||||
ui->action_Start->setEnabled(false);
|
ui->action_Start->setEnabled(false);
|
||||||
|
@ -1203,6 +1231,7 @@ void GMainWindow::ShutdownGame() {
|
||||||
ui->action_Advance_Frame->setEnabled(false);
|
ui->action_Advance_Frame->setEnabled(false);
|
||||||
ui->action_Capture_Screenshot->setEnabled(false);
|
ui->action_Capture_Screenshot->setEnabled(false);
|
||||||
render_window->hide();
|
render_window->hide();
|
||||||
|
secondary_window->hide();
|
||||||
loading_screen->hide();
|
loading_screen->hide();
|
||||||
loading_screen->Clear();
|
loading_screen->Clear();
|
||||||
if (game_list->IsEmpty())
|
if (game_list->IsEmpty())
|
||||||
|
@ -1236,6 +1265,7 @@ void GMainWindow::ShutdownGame() {
|
||||||
|
|
||||||
// When closing the game, destroy the GLWindow to clear the context after the game is closed
|
// When closing the game, destroy the GLWindow to clear the context after the game is closed
|
||||||
render_window->ReleaseRenderTarget();
|
render_window->ReleaseRenderTarget();
|
||||||
|
secondary_window->ReleaseRenderTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::StoreRecentFile(const QString& filename) {
|
void GMainWindow::StoreRecentFile(const QString& filename) {
|
||||||
|
@ -1636,6 +1666,7 @@ void GMainWindow::OnStopGame() {
|
||||||
|
|
||||||
void GMainWindow::OnLoadComplete() {
|
void GMainWindow::OnLoadComplete() {
|
||||||
loading_screen->OnLoadComplete();
|
loading_screen->OnLoadComplete();
|
||||||
|
UpdateSecondaryWindowVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::OnMenuReportCompatibility() {
|
void GMainWindow::OnMenuReportCompatibility() {
|
||||||
|
@ -1660,6 +1691,17 @@ void GMainWindow::ToggleFullscreen() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GMainWindow::ToggleSecondaryFullscreen() {
|
||||||
|
if (!emulation_running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (secondary_window->isFullScreen()) {
|
||||||
|
secondary_window->showNormal();
|
||||||
|
} else {
|
||||||
|
secondary_window->showFullScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GMainWindow::ShowFullscreen() {
|
void GMainWindow::ShowFullscreen() {
|
||||||
if (ui->action_Single_Window_Mode->isChecked()) {
|
if (ui->action_Single_Window_Mode->isChecked()) {
|
||||||
UISettings::values.geometry = saveGeometry();
|
UISettings::values.geometry = saveGeometry();
|
||||||
|
@ -1709,6 +1751,19 @@ void GMainWindow::ToggleWindowMode() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GMainWindow::UpdateSecondaryWindowVisibility() {
|
||||||
|
if (!emulation_running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Settings::values.layout_option == Settings::LayoutOption::SeparateWindows) {
|
||||||
|
secondary_window->RestoreGeometry();
|
||||||
|
secondary_window->show();
|
||||||
|
} else {
|
||||||
|
secondary_window->BackupGeometry();
|
||||||
|
secondary_window->hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GMainWindow::ChangeScreenLayout() {
|
void GMainWindow::ChangeScreenLayout() {
|
||||||
Settings::LayoutOption new_layout = Settings::LayoutOption::Default;
|
Settings::LayoutOption new_layout = Settings::LayoutOption::Default;
|
||||||
|
|
||||||
|
@ -1720,35 +1775,38 @@ void GMainWindow::ChangeScreenLayout() {
|
||||||
new_layout = Settings::LayoutOption::LargeScreen;
|
new_layout = Settings::LayoutOption::LargeScreen;
|
||||||
} else if (ui->action_Screen_Layout_Side_by_Side->isChecked()) {
|
} else if (ui->action_Screen_Layout_Side_by_Side->isChecked()) {
|
||||||
new_layout = Settings::LayoutOption::SideScreen;
|
new_layout = Settings::LayoutOption::SideScreen;
|
||||||
|
} else if (ui->action_Screen_Layout_Separate_Windows->isChecked()) {
|
||||||
|
new_layout = Settings::LayoutOption::SeparateWindows;
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings::values.layout_option = new_layout;
|
Settings::values.layout_option = new_layout;
|
||||||
Settings::Apply();
|
Settings::Apply();
|
||||||
|
UpdateSecondaryWindowVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::ToggleScreenLayout() {
|
void GMainWindow::ToggleScreenLayout() {
|
||||||
Settings::LayoutOption new_layout = Settings::LayoutOption::Default;
|
const Settings::LayoutOption new_layout = []() {
|
||||||
|
switch (Settings::values.layout_option) {
|
||||||
switch (Settings::values.layout_option) {
|
case Settings::LayoutOption::Default:
|
||||||
case Settings::LayoutOption::Default:
|
return Settings::LayoutOption::SingleScreen;
|
||||||
new_layout = Settings::LayoutOption::SingleScreen;
|
case Settings::LayoutOption::SingleScreen:
|
||||||
break;
|
return Settings::LayoutOption::LargeScreen;
|
||||||
case Settings::LayoutOption::SingleScreen:
|
case Settings::LayoutOption::LargeScreen:
|
||||||
new_layout = Settings::LayoutOption::LargeScreen;
|
return Settings::LayoutOption::SideScreen;
|
||||||
break;
|
case Settings::LayoutOption::SideScreen:
|
||||||
case Settings::LayoutOption::LargeScreen:
|
return Settings::LayoutOption::SeparateWindows;
|
||||||
new_layout = Settings::LayoutOption::SideScreen;
|
case Settings::LayoutOption::SeparateWindows:
|
||||||
break;
|
return Settings::LayoutOption::Default;
|
||||||
case Settings::LayoutOption::SideScreen:
|
default:
|
||||||
new_layout = Settings::LayoutOption::Default;
|
LOG_ERROR(Frontend, "Unknown layout option {}", Settings::values.layout_option);
|
||||||
break;
|
return Settings::LayoutOption::Default;
|
||||||
default:
|
}
|
||||||
LOG_ERROR(Frontend, "Unknown layout option {}", Settings::values.layout_option);
|
}();
|
||||||
}
|
|
||||||
|
|
||||||
Settings::values.layout_option = new_layout;
|
Settings::values.layout_option = new_layout;
|
||||||
SyncMenuUISettings();
|
SyncMenuUISettings();
|
||||||
Settings::Apply();
|
Settings::Apply();
|
||||||
|
UpdateSecondaryWindowVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::OnSwapScreens() {
|
void GMainWindow::OnSwapScreens() {
|
||||||
|
@ -1813,6 +1871,7 @@ void GMainWindow::OnConfigure() {
|
||||||
} else {
|
} else {
|
||||||
setMouseTracking(false);
|
setMouseTracking(false);
|
||||||
}
|
}
|
||||||
|
UpdateSecondaryWindowVisibility();
|
||||||
} else {
|
} else {
|
||||||
Settings::values.input_profiles = old_input_profiles;
|
Settings::values.input_profiles = old_input_profiles;
|
||||||
Settings::values.touch_from_button_maps = old_touch_from_button_maps;
|
Settings::values.touch_from_button_maps = old_touch_from_button_maps;
|
||||||
|
@ -1991,7 +2050,9 @@ void GMainWindow::OnCaptureScreenshot() {
|
||||||
const QString timestamp =
|
const QString timestamp =
|
||||||
QDateTime::currentDateTime().toString(QStringLiteral("dd.MM.yy_hh.mm.ss.z"));
|
QDateTime::currentDateTime().toString(QStringLiteral("dd.MM.yy_hh.mm.ss.z"));
|
||||||
path.append(QStringLiteral("/%1_%2.png").arg(filename, timestamp));
|
path.append(QStringLiteral("/%1_%2.png").arg(filename, timestamp));
|
||||||
render_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor, path);
|
|
||||||
|
auto* const screenshot_window = secondary_window->HasFocus() ? secondary_window : render_window;
|
||||||
|
screenshot_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor, path);
|
||||||
OnStartGame();
|
OnStartGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2227,7 +2288,9 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
|
||||||
ShutdownGame();
|
ShutdownGame();
|
||||||
|
|
||||||
render_window->close();
|
render_window->close();
|
||||||
|
secondary_window->close();
|
||||||
multiplayer_state->Close();
|
multiplayer_state->Close();
|
||||||
|
InputCommon::Shutdown();
|
||||||
QWidget::closeEvent(event);
|
QWidget::closeEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2412,6 +2475,8 @@ void GMainWindow::SyncMenuUISettings() {
|
||||||
Settings::LayoutOption::LargeScreen);
|
Settings::LayoutOption::LargeScreen);
|
||||||
ui->action_Screen_Layout_Side_by_Side->setChecked(Settings::values.layout_option ==
|
ui->action_Screen_Layout_Side_by_Side->setChecked(Settings::values.layout_option ==
|
||||||
Settings::LayoutOption::SideScreen);
|
Settings::LayoutOption::SideScreen);
|
||||||
|
ui->action_Screen_Layout_Separate_Windows->setChecked(Settings::values.layout_option ==
|
||||||
|
Settings::LayoutOption::SeparateWindows);
|
||||||
ui->action_Screen_Layout_Swap_Screens->setChecked(Settings::values.swap_screen);
|
ui->action_Screen_Layout_Swap_Screens->setChecked(Settings::values.swap_screen);
|
||||||
ui->action_Screen_Layout_Upright_Screens->setChecked(Settings::values.upright_screen);
|
ui->action_Screen_Layout_Upright_Screens->setChecked(Settings::values.upright_screen);
|
||||||
}
|
}
|
||||||
|
|
|
@ -197,7 +197,9 @@ private slots:
|
||||||
void OnDisplayTitleBars(bool);
|
void OnDisplayTitleBars(bool);
|
||||||
void InitializeHotkeys();
|
void InitializeHotkeys();
|
||||||
void ToggleFullscreen();
|
void ToggleFullscreen();
|
||||||
|
void ToggleSecondaryFullscreen();
|
||||||
void ChangeScreenLayout();
|
void ChangeScreenLayout();
|
||||||
|
void UpdateSecondaryWindowVisibility();
|
||||||
void ToggleScreenLayout();
|
void ToggleScreenLayout();
|
||||||
void OnSwapScreens();
|
void OnSwapScreens();
|
||||||
void OnRotateScreens();
|
void OnRotateScreens();
|
||||||
|
@ -238,6 +240,7 @@ private:
|
||||||
std::unique_ptr<Ui::MainWindow> ui;
|
std::unique_ptr<Ui::MainWindow> ui;
|
||||||
|
|
||||||
GRenderWindow* render_window;
|
GRenderWindow* render_window;
|
||||||
|
GRenderWindow* secondary_window;
|
||||||
|
|
||||||
GameListPlaceholder* game_list_placeholder;
|
GameListPlaceholder* game_list_placeholder;
|
||||||
LoadingScreen* loading_screen;
|
LoadingScreen* loading_screen;
|
||||||
|
|
|
@ -125,6 +125,7 @@
|
||||||
<addaction name="action_Screen_Layout_Single_Screen"/>
|
<addaction name="action_Screen_Layout_Single_Screen"/>
|
||||||
<addaction name="action_Screen_Layout_Large_Screen"/>
|
<addaction name="action_Screen_Layout_Large_Screen"/>
|
||||||
<addaction name="action_Screen_Layout_Side_by_Side"/>
|
<addaction name="action_Screen_Layout_Side_by_Side"/>
|
||||||
|
<addaction name="action_Screen_Layout_Separate_Windows"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="action_Screen_Layout_Upright_Screens"/>
|
<addaction name="action_Screen_Layout_Upright_Screens"/>
|
||||||
<addaction name="action_Screen_Layout_Swap_Screens"/>
|
<addaction name="action_Screen_Layout_Swap_Screens"/>
|
||||||
|
@ -471,6 +472,14 @@
|
||||||
<string>Side by Side</string>
|
<string>Side by Side</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="action_Screen_Layout_Separate_Windows">
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Separate Windows</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
<action name="action_Screen_Layout_Swap_Screens">
|
<action name="action_Screen_Layout_Swap_Screens">
|
||||||
<property name="checkable">
|
<property name="checkable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
|
|
|
@ -247,7 +247,8 @@ System::ResultStatus System::SingleStep() {
|
||||||
return RunLoop(false);
|
return RunLoop(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
|
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath,
|
||||||
|
Frontend::EmuWindow* secondary_window) {
|
||||||
FileUtil::SetCurrentRomPath(filepath);
|
FileUtil::SetCurrentRomPath(filepath);
|
||||||
app_loader = Loader::GetLoader(filepath);
|
app_loader = Loader::GetLoader(filepath);
|
||||||
if (!app_loader) {
|
if (!app_loader) {
|
||||||
|
@ -278,7 +279,8 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
|
||||||
if (Settings::values.is_new_3ds) {
|
if (Settings::values.is_new_3ds) {
|
||||||
num_cores = 4;
|
num_cores = 4;
|
||||||
}
|
}
|
||||||
ResultStatus init_result{Init(emu_window, *system_mode.first, *n3ds_mode.first, num_cores)};
|
ResultStatus init_result{
|
||||||
|
Init(emu_window, secondary_window, *system_mode.first, *n3ds_mode.first, num_cores)};
|
||||||
if (init_result != ResultStatus::Success) {
|
if (init_result != ResultStatus::Success) {
|
||||||
LOG_CRITICAL(Core, "Failed to initialize system (Error {})!",
|
LOG_CRITICAL(Core, "Failed to initialize system (Error {})!",
|
||||||
static_cast<u32>(init_result));
|
static_cast<u32>(init_result));
|
||||||
|
@ -324,6 +326,7 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
|
||||||
|
|
||||||
status = ResultStatus::Success;
|
status = ResultStatus::Success;
|
||||||
m_emu_window = &emu_window;
|
m_emu_window = &emu_window;
|
||||||
|
m_secondary_window = secondary_window;
|
||||||
m_filepath = filepath;
|
m_filepath = filepath;
|
||||||
self_delete_pending = false;
|
self_delete_pending = false;
|
||||||
|
|
||||||
|
@ -355,8 +358,9 @@ void System::Reschedule() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mode, u8 n3ds_mode,
|
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window,
|
||||||
u32 num_cores) {
|
Frontend::EmuWindow* secondary_window, u32 system_mode,
|
||||||
|
u8 n3ds_mode, u32 num_cores) {
|
||||||
LOG_DEBUG(HW_Memory, "initialized OK");
|
LOG_DEBUG(HW_Memory, "initialized OK");
|
||||||
|
|
||||||
memory = std::make_unique<Memory::MemorySystem>();
|
memory = std::make_unique<Memory::MemorySystem>();
|
||||||
|
@ -420,7 +424,7 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
|
||||||
video_dumper = std::make_unique<VideoDumper::NullBackend>();
|
video_dumper = std::make_unique<VideoDumper::NullBackend>();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
VideoCore::ResultStatus result = VideoCore::Init(emu_window, *memory);
|
VideoCore::ResultStatus result = VideoCore::Init(emu_window, secondary_window, *memory);
|
||||||
if (result != VideoCore::ResultStatus::Success) {
|
if (result != VideoCore::ResultStatus::Success) {
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case VideoCore::ResultStatus::ErrorGenericDrivers:
|
case VideoCore::ResultStatus::ErrorGenericDrivers:
|
||||||
|
@ -586,7 +590,8 @@ void System::Reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload the system with the same setting
|
// Reload the system with the same setting
|
||||||
[[maybe_unused]] const System::ResultStatus result = Load(*m_emu_window, m_filepath);
|
[[maybe_unused]] const System::ResultStatus result =
|
||||||
|
Load(*m_emu_window, m_filepath, m_secondary_window);
|
||||||
|
|
||||||
// Restore the deliver arg.
|
// Restore the deliver arg.
|
||||||
if (auto apt = Service::APT::GetModule(*this)) {
|
if (auto apt = Service::APT::GetModule(*this)) {
|
||||||
|
@ -611,8 +616,8 @@ void System::serialize(Archive& ar, const unsigned int file_version) {
|
||||||
// Re-initialize everything like it was before
|
// Re-initialize everything like it was before
|
||||||
auto system_mode = this->app_loader->LoadKernelSystemMode();
|
auto system_mode = this->app_loader->LoadKernelSystemMode();
|
||||||
auto n3ds_mode = this->app_loader->LoadKernelN3dsMode();
|
auto n3ds_mode = this->app_loader->LoadKernelN3dsMode();
|
||||||
[[maybe_unused]] const System::ResultStatus result =
|
[[maybe_unused]] const System::ResultStatus result = Init(
|
||||||
Init(*m_emu_window, *system_mode.first, *n3ds_mode.first, num_cores);
|
*m_emu_window, m_secondary_window, *system_mode.first, *n3ds_mode.first, num_cores);
|
||||||
}
|
}
|
||||||
|
|
||||||
// flush on save, don't flush on load
|
// flush on save, don't flush on load
|
||||||
|
|
|
@ -143,7 +143,8 @@ public:
|
||||||
* @param filepath String path to the executable application to load on the host file system.
|
* @param filepath String path to the executable application to load on the host file system.
|
||||||
* @returns ResultStatus code, indicating if the operation succeeded.
|
* @returns ResultStatus code, indicating if the operation succeeded.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] ResultStatus Load(Frontend::EmuWindow& emu_window, const std::string& filepath);
|
[[nodiscard]] ResultStatus Load(Frontend::EmuWindow& emu_window, const std::string& filepath,
|
||||||
|
Frontend::EmuWindow* secondary_window = {});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if the emulated system is powered on (all subsystems initialized and able to run an
|
* Indicates if the emulated system is powered on (all subsystems initialized and able to run an
|
||||||
|
@ -324,8 +325,9 @@ private:
|
||||||
* @param system_mode The system mode.
|
* @param system_mode The system mode.
|
||||||
* @return ResultStatus code, indicating if the operation succeeded.
|
* @return ResultStatus code, indicating if the operation succeeded.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] ResultStatus Init(Frontend::EmuWindow& emu_window, u32 system_mode, u8 n3ds_mode,
|
[[nodiscard]] ResultStatus Init(Frontend::EmuWindow& emu_window,
|
||||||
u32 num_cores);
|
Frontend::EmuWindow* secondary_window, u32 system_mode,
|
||||||
|
u8 n3ds_mode, u32 num_cores);
|
||||||
|
|
||||||
/// Reschedule the core emulation
|
/// Reschedule the core emulation
|
||||||
void Reschedule();
|
void Reschedule();
|
||||||
|
@ -385,6 +387,7 @@ private:
|
||||||
std::string status_details = "";
|
std::string status_details = "";
|
||||||
/// Saved variables for reset
|
/// Saved variables for reset
|
||||||
Frontend::EmuWindow* m_emu_window;
|
Frontend::EmuWindow* m_emu_window;
|
||||||
|
Frontend::EmuWindow* m_secondary_window;
|
||||||
std::string m_filepath;
|
std::string m_filepath;
|
||||||
std::string m_chainloadpath;
|
std::string m_chainloadpath;
|
||||||
u64 title_id;
|
u64 title_id;
|
||||||
|
|
|
@ -4,12 +4,13 @@
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include "core/3ds.h"
|
|
||||||
#include "core/frontend/emu_window.h"
|
#include "core/frontend/emu_window.h"
|
||||||
#include "core/frontend/input.h"
|
#include "core/frontend/input.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
|
||||||
namespace Frontend {
|
namespace Frontend {
|
||||||
|
/// We need a global touch state that is shared across the different window instances
|
||||||
|
static std::weak_ptr<EmuWindow::TouchState> global_touch_state;
|
||||||
|
|
||||||
GraphicsContext::~GraphicsContext() = default;
|
GraphicsContext::~GraphicsContext() = default;
|
||||||
|
|
||||||
|
@ -45,18 +46,14 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
EmuWindow::EmuWindow() {
|
EmuWindow::EmuWindow() {
|
||||||
// TODO: Find a better place to set this.
|
CreateTouchState();
|
||||||
config.min_client_area_size =
|
};
|
||||||
std::make_pair(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
|
|
||||||
active_config = config;
|
EmuWindow::EmuWindow(bool is_secondary_) : is_secondary{is_secondary_} {
|
||||||
touch_state = std::make_shared<TouchState>();
|
CreateTouchState();
|
||||||
Input::RegisterFactory<Input::TouchDevice>("emu_window", touch_state);
|
|
||||||
}
|
|
||||||
|
|
||||||
EmuWindow::~EmuWindow() {
|
|
||||||
Input::UnregisterFactory<Input::TouchDevice>("emu_window");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EmuWindow::~EmuWindow() = default;
|
||||||
/**
|
/**
|
||||||
* Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout
|
* Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout
|
||||||
* @param layout FramebufferLayout object describing the framebuffer size and screen positions
|
* @param layout FramebufferLayout object describing the framebuffer size and screen positions
|
||||||
|
@ -111,6 +108,15 @@ std::tuple<unsigned, unsigned> EmuWindow::ClipToTouchScreen(unsigned new_x, unsi
|
||||||
return std::make_tuple(new_x, new_y);
|
return std::make_tuple(new_x, new_y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EmuWindow::CreateTouchState() {
|
||||||
|
if (touch_state = global_touch_state.lock()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
touch_state = std::make_shared<TouchState>();
|
||||||
|
Input::RegisterFactory<Input::TouchDevice>("emu_window", touch_state);
|
||||||
|
global_touch_state = touch_state;
|
||||||
|
}
|
||||||
|
|
||||||
bool EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) {
|
bool EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) {
|
||||||
if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y))
|
if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y))
|
||||||
return false;
|
return false;
|
||||||
|
@ -194,6 +200,12 @@ void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height,
|
||||||
layout = Layout::SideFrameLayout(width, height, Settings::values.swap_screen,
|
layout = Layout::SideFrameLayout(width, height, Settings::values.swap_screen,
|
||||||
Settings::values.upright_screen);
|
Settings::values.upright_screen);
|
||||||
break;
|
break;
|
||||||
|
#ifndef ANDROID
|
||||||
|
case Settings::LayoutOption::SeparateWindows:
|
||||||
|
layout = Layout::SeparateWindowsLayout(width, height, is_secondary,
|
||||||
|
Settings::values.upright_screen);
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
case Settings::LayoutOption::MobilePortrait:
|
case Settings::LayoutOption::MobilePortrait:
|
||||||
layout = Layout::MobilePortraitFrameLayout(width, height, Settings::values.swap_screen);
|
layout = Layout::MobilePortraitFrameLayout(width, height, Settings::values.swap_screen);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -7,7 +7,9 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
#include "core/3ds.h"
|
||||||
#include "core/frontend/framebuffer_layout.h"
|
#include "core/frontend/framebuffer_layout.h"
|
||||||
|
|
||||||
namespace Frontend {
|
namespace Frontend {
|
||||||
|
@ -87,12 +89,15 @@ public:
|
||||||
*/
|
*/
|
||||||
class EmuWindow : public GraphicsContext {
|
class EmuWindow : public GraphicsContext {
|
||||||
public:
|
public:
|
||||||
|
class TouchState;
|
||||||
|
|
||||||
/// Data structure to store emuwindow configuration
|
/// Data structure to store emuwindow configuration
|
||||||
struct WindowConfig {
|
struct WindowConfig {
|
||||||
bool fullscreen = false;
|
bool fullscreen = false;
|
||||||
int res_width = 0;
|
int res_width = 0;
|
||||||
int res_height = 0;
|
int res_height = 0;
|
||||||
std::pair<unsigned, unsigned> min_client_area_size;
|
std::pair<unsigned, unsigned> min_client_area_size{
|
||||||
|
Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight};
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Polls window events
|
/// Polls window events
|
||||||
|
@ -177,6 +182,7 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
EmuWindow();
|
EmuWindow();
|
||||||
|
EmuWindow(bool is_secondary);
|
||||||
virtual ~EmuWindow();
|
virtual ~EmuWindow();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -204,6 +210,8 @@ protected:
|
||||||
framebuffer_layout = layout;
|
framebuffer_layout = layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_secondary{};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Handler called when the minimal client area was requested to be changed via SetConfig.
|
* Handler called when the minimal client area was requested to be changed via SetConfig.
|
||||||
|
@ -214,13 +222,14 @@ private:
|
||||||
// By default, ignore this request and do nothing.
|
// By default, ignore this request and do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CreateTouchState();
|
||||||
|
|
||||||
Layout::FramebufferLayout framebuffer_layout; ///< Current framebuffer layout
|
Layout::FramebufferLayout framebuffer_layout; ///< Current framebuffer layout
|
||||||
|
|
||||||
WindowConfig config; ///< Internal configuration (changes pending for being applied in
|
WindowConfig config{}; ///< Internal configuration (changes pending for being applied in
|
||||||
/// ProcessConfigurationChanges)
|
/// ProcessConfigurationChanges)
|
||||||
WindowConfig active_config; ///< Internal active configuration
|
WindowConfig active_config{}; ///< Internal active configuration
|
||||||
|
|
||||||
class TouchState;
|
|
||||||
std::shared_ptr<TouchState> touch_state;
|
std::shared_ptr<TouchState> touch_state;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -342,6 +342,13 @@ FramebufferLayout SideFrameLayout(u32 width, u32 height, bool swapped, bool upri
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FramebufferLayout SeparateWindowsLayout(u32 width, u32 height, bool is_secondary, bool upright) {
|
||||||
|
// When is_secondary is true, we disable the top screen, and enable the bottom screen.
|
||||||
|
// The same logic is found in the SingleFrameLayout using the is_swapped bool.
|
||||||
|
is_secondary = Settings::values.swap_screen ? !is_secondary : is_secondary;
|
||||||
|
return SingleFrameLayout(width, height, is_secondary, upright);
|
||||||
|
}
|
||||||
|
|
||||||
FramebufferLayout CustomFrameLayout(u32 width, u32 height) {
|
FramebufferLayout CustomFrameLayout(u32 width, u32 height) {
|
||||||
ASSERT(width > 0);
|
ASSERT(width > 0);
|
||||||
ASSERT(height > 0);
|
ASSERT(height > 0);
|
||||||
|
@ -360,7 +367,7 @@ FramebufferLayout CustomFrameLayout(u32 width, u32 height) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale) {
|
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondary) {
|
||||||
FramebufferLayout layout;
|
FramebufferLayout layout;
|
||||||
if (Settings::values.custom_layout == true) {
|
if (Settings::values.custom_layout == true) {
|
||||||
layout = CustomFrameLayout(
|
layout = CustomFrameLayout(
|
||||||
|
@ -370,8 +377,13 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale) {
|
||||||
int width, height;
|
int width, height;
|
||||||
switch (Settings::values.layout_option) {
|
switch (Settings::values.layout_option) {
|
||||||
case Settings::LayoutOption::SingleScreen:
|
case Settings::LayoutOption::SingleScreen:
|
||||||
|
#ifndef ANDROID
|
||||||
|
case Settings::LayoutOption::SeparateWindows:
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
const bool swap_screens = is_secondary || Settings::values.swap_screen;
|
||||||
if (Settings::values.upright_screen) {
|
if (Settings::values.upright_screen) {
|
||||||
if (Settings::values.swap_screen) {
|
if (swap_screens) {
|
||||||
width = Core::kScreenBottomHeight * res_scale;
|
width = Core::kScreenBottomHeight * res_scale;
|
||||||
height = Core::kScreenBottomWidth * res_scale;
|
height = Core::kScreenBottomWidth * res_scale;
|
||||||
} else {
|
} else {
|
||||||
|
@ -379,7 +391,7 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale) {
|
||||||
height = Core::kScreenTopWidth * res_scale;
|
height = Core::kScreenTopWidth * res_scale;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (Settings::values.swap_screen) {
|
if (swap_screens) {
|
||||||
width = Core::kScreenBottomWidth * res_scale;
|
width = Core::kScreenBottomWidth * res_scale;
|
||||||
height = Core::kScreenBottomHeight * res_scale;
|
height = Core::kScreenBottomHeight * res_scale;
|
||||||
} else {
|
} else {
|
||||||
|
@ -387,9 +399,10 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale) {
|
||||||
height = Core::kScreenTopHeight * res_scale;
|
height = Core::kScreenTopHeight * res_scale;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
layout = SingleFrameLayout(width, height, Settings::values.swap_screen,
|
layout =
|
||||||
Settings::values.upright_screen);
|
SingleFrameLayout(width, height, swap_screens, Settings::values.upright_screen);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case Settings::LayoutOption::LargeScreen:
|
case Settings::LayoutOption::LargeScreen:
|
||||||
if (Settings::values.upright_screen) {
|
if (Settings::values.upright_screen) {
|
||||||
if (Settings::values.swap_screen) {
|
if (Settings::values.swap_screen) {
|
||||||
|
@ -544,6 +557,9 @@ std::pair<unsigned, unsigned> GetMinimumSizeFromLayout(Settings::LayoutOption la
|
||||||
|
|
||||||
switch (layout) {
|
switch (layout) {
|
||||||
case Settings::LayoutOption::SingleScreen:
|
case Settings::LayoutOption::SingleScreen:
|
||||||
|
#ifndef ANDROID
|
||||||
|
case Settings::LayoutOption::SeparateWindows:
|
||||||
|
#endif
|
||||||
min_width = Settings::values.swap_screen ? Core::kScreenBottomWidth : Core::kScreenTopWidth;
|
min_width = Settings::values.swap_screen ? Core::kScreenBottomWidth : Core::kScreenTopWidth;
|
||||||
min_height = Core::kScreenBottomHeight;
|
min_height = Core::kScreenBottomHeight;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -98,6 +98,16 @@ FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool is_swapped, bool
|
||||||
*/
|
*/
|
||||||
FramebufferLayout SideFrameLayout(u32 width, u32 height, bool is_swapped, bool upright);
|
FramebufferLayout SideFrameLayout(u32 width, u32 height, bool is_swapped, bool upright);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method for constructing a Frame with the Top screen and bottom
|
||||||
|
* screen on separate windows
|
||||||
|
* @param width Window framebuffer width in pixels
|
||||||
|
* @param height Window framebuffer height in pixels
|
||||||
|
* @param is_secondary if true, the bottom screen will be enabled instead of the top screen
|
||||||
|
* @return Newly created FramebufferLayout object with default screen regions initialized
|
||||||
|
*/
|
||||||
|
FramebufferLayout SeparateWindowsLayout(u32 width, u32 height, bool is_secondary, bool upright);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory method for constructing a custom FramebufferLayout
|
* Factory method for constructing a custom FramebufferLayout
|
||||||
* @param width Window framebuffer width in pixels
|
* @param width Window framebuffer width in pixels
|
||||||
|
@ -111,7 +121,7 @@ FramebufferLayout CustomFrameLayout(u32 width, u32 height);
|
||||||
* Read from the current settings to determine which layout to use.
|
* Read from the current settings to determine which layout to use.
|
||||||
* @param res_scale resolution scale factor
|
* @param res_scale resolution scale factor
|
||||||
*/
|
*/
|
||||||
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale);
|
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondary = false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method for transforming a frame layout when using Cardboard VR
|
* Convenience method for transforming a frame layout when using Cardboard VR
|
||||||
|
|
|
@ -24,7 +24,9 @@ enum class LayoutOption {
|
||||||
SingleScreen,
|
SingleScreen,
|
||||||
LargeScreen,
|
LargeScreen,
|
||||||
SideScreen,
|
SideScreen,
|
||||||
|
#ifndef ANDROID
|
||||||
|
SeparateWindows,
|
||||||
|
#endif
|
||||||
// Similiar to default, but better for mobile devices in portrait mode. Top screen in clamped to
|
// Similiar to default, but better for mobile devices in portrait mode. Top screen in clamped to
|
||||||
// the top of the frame, and the bottom screen is enlarged to match the top screen.
|
// the top of the frame, and the bottom screen is enlarged to match the top screen.
|
||||||
MobilePortrait,
|
MobilePortrait,
|
||||||
|
|
|
@ -56,6 +56,7 @@ void Shutdown() {
|
||||||
Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
|
Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
|
||||||
Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
|
Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
|
||||||
motion_emu.reset();
|
motion_emu.reset();
|
||||||
|
Input::UnregisterFactory<Input::TouchDevice>("emu_window");
|
||||||
Input::UnregisterFactory<Input::TouchDevice>("touch_from_button");
|
Input::UnregisterFactory<Input::TouchDevice>("touch_from_button");
|
||||||
sdl.reset();
|
sdl.reset();
|
||||||
udp.reset();
|
udp.reset();
|
||||||
|
|
|
@ -9,11 +9,20 @@
|
||||||
#include "video_core/swrasterizer/swrasterizer.h"
|
#include "video_core/swrasterizer/swrasterizer.h"
|
||||||
#include "video_core/video_core.h"
|
#include "video_core/video_core.h"
|
||||||
|
|
||||||
RendererBase::RendererBase(Frontend::EmuWindow& window) : render_window{window} {}
|
RendererBase::RendererBase(Frontend::EmuWindow& window, Frontend::EmuWindow* secondary_window_)
|
||||||
|
: render_window{window}, secondary_window{secondary_window_} {}
|
||||||
|
|
||||||
RendererBase::~RendererBase() = default;
|
RendererBase::~RendererBase() = default;
|
||||||
|
|
||||||
void RendererBase::UpdateCurrentFramebufferLayout(bool is_portrait_mode) {
|
void RendererBase::UpdateCurrentFramebufferLayout(bool is_portrait_mode) {
|
||||||
const Layout::FramebufferLayout& layout = render_window.GetFramebufferLayout();
|
const auto update_layout = [is_portrait_mode](Frontend::EmuWindow& window) {
|
||||||
render_window.UpdateCurrentFramebufferLayout(layout.width, layout.height, is_portrait_mode);
|
const Layout::FramebufferLayout& layout = window.GetFramebufferLayout();
|
||||||
|
window.UpdateCurrentFramebufferLayout(layout.width, layout.height, is_portrait_mode);
|
||||||
|
};
|
||||||
|
update_layout(render_window);
|
||||||
|
if (secondary_window) {
|
||||||
|
update_layout(*secondary_window);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererBase::RefreshRasterizerSetting() {
|
void RendererBase::RefreshRasterizerSetting() {
|
||||||
|
|
|
@ -15,7 +15,7 @@ class EmuWindow;
|
||||||
|
|
||||||
class RendererBase : NonCopyable {
|
class RendererBase : NonCopyable {
|
||||||
public:
|
public:
|
||||||
explicit RendererBase(Frontend::EmuWindow& window);
|
explicit RendererBase(Frontend::EmuWindow& window, Frontend::EmuWindow* secondary_window);
|
||||||
virtual ~RendererBase();
|
virtual ~RendererBase();
|
||||||
|
|
||||||
/// Initialize the renderer
|
/// Initialize the renderer
|
||||||
|
@ -29,7 +29,10 @@ public:
|
||||||
|
|
||||||
/// Draws the latest frame to the window waiting timeout_ms for a frame to arrive (Renderer
|
/// Draws the latest frame to the window waiting timeout_ms for a frame to arrive (Renderer
|
||||||
/// specific implementation)
|
/// specific implementation)
|
||||||
virtual void TryPresent(int timeout_ms) = 0;
|
virtual void TryPresent(int timeout_ms, bool is_secondary) = 0;
|
||||||
|
virtual void TryPresent(int timeout_ms) {
|
||||||
|
TryPresent(timeout_ms, false);
|
||||||
|
}
|
||||||
|
|
||||||
/// Prepares for video dumping (e.g. create necessary buffers, etc)
|
/// Prepares for video dumping (e.g. create necessary buffers, etc)
|
||||||
virtual void PrepareVideoDumping() = 0;
|
virtual void PrepareVideoDumping() = 0;
|
||||||
|
@ -67,7 +70,8 @@ public:
|
||||||
void Sync();
|
void Sync();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
|
Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
|
||||||
|
Frontend::EmuWindow* secondary_window; ///< Reference to the secondary render window handle.
|
||||||
std::unique_ptr<VideoCore::RasterizerInterface> rasterizer;
|
std::unique_ptr<VideoCore::RasterizerInterface> rasterizer;
|
||||||
f32 m_current_fps = 0.0f; ///< Current framerate, should be set by the renderer
|
f32 m_current_fps = 0.0f; ///< Current framerate, should be set by the renderer
|
||||||
int m_current_frame = 0; ///< Current frame, should be set by the renderer
|
int m_current_frame = 0; ///< Current frame, should be set by the renderer
|
||||||
|
|
|
@ -352,10 +352,13 @@ static std::array<GLfloat, 3 * 2> MakeOrthographicMatrix(const float width, cons
|
||||||
return matrix;
|
return matrix;
|
||||||
}
|
}
|
||||||
|
|
||||||
RendererOpenGL::RendererOpenGL(Frontend::EmuWindow& window)
|
RendererOpenGL::RendererOpenGL(Frontend::EmuWindow& window, Frontend::EmuWindow* secondary_window)
|
||||||
: RendererBase{window}, frame_dumper(Core::System::GetInstance().VideoDumper(), window) {
|
: RendererBase{window, secondary_window},
|
||||||
|
frame_dumper(Core::System::GetInstance().VideoDumper(), window) {
|
||||||
window.mailbox = std::make_unique<OGLTextureMailbox>();
|
window.mailbox = std::make_unique<OGLTextureMailbox>();
|
||||||
|
if (secondary_window) {
|
||||||
|
secondary_window->mailbox = std::make_unique<OGLTextureMailbox>();
|
||||||
|
}
|
||||||
frame_dumper.mailbox = std::make_unique<OGLVideoDumpingMailbox>();
|
frame_dumper.mailbox = std::make_unique<OGLVideoDumpingMailbox>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,9 +377,17 @@ void RendererOpenGL::SwapBuffers() {
|
||||||
|
|
||||||
RenderScreenshot();
|
RenderScreenshot();
|
||||||
|
|
||||||
const auto& layout = render_window.GetFramebufferLayout();
|
const auto& main_layout = render_window.GetFramebufferLayout();
|
||||||
RenderToMailbox(layout, render_window.mailbox, false);
|
RenderToMailbox(main_layout, render_window.mailbox, false);
|
||||||
|
|
||||||
|
#ifndef ANDROID
|
||||||
|
if (Settings::values.layout_option == Settings::LayoutOption::SeparateWindows) {
|
||||||
|
ASSERT(secondary_window);
|
||||||
|
const auto& secondary_layout = secondary_window->GetFramebufferLayout();
|
||||||
|
RenderToMailbox(secondary_layout, secondary_window->mailbox, false);
|
||||||
|
secondary_window->PollEvents();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (frame_dumper.IsDumping()) {
|
if (frame_dumper.IsDumping()) {
|
||||||
try {
|
try {
|
||||||
RenderToMailbox(frame_dumper.GetLayout(), frame_dumper.mailbox, true);
|
RenderToMailbox(frame_dumper.GetLayout(), frame_dumper.mailbox, true);
|
||||||
|
@ -1112,9 +1123,10 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererOpenGL::TryPresent(int timeout_ms) {
|
void RendererOpenGL::TryPresent(int timeout_ms, bool is_secondary) {
|
||||||
const auto& layout = render_window.GetFramebufferLayout();
|
const auto& window = is_secondary ? *secondary_window : render_window;
|
||||||
auto frame = render_window.mailbox->TryGetPresentFrame(timeout_ms);
|
const auto& layout = window.GetFramebufferLayout();
|
||||||
|
auto frame = window.mailbox->TryGetPresentFrame(timeout_ms);
|
||||||
if (!frame) {
|
if (!frame) {
|
||||||
LOG_DEBUG(Render_OpenGL, "TryGetPresentFrame returned no frame to present");
|
LOG_DEBUG(Render_OpenGL, "TryGetPresentFrame returned no frame to present");
|
||||||
return;
|
return;
|
||||||
|
@ -1127,7 +1139,7 @@ void RendererOpenGL::TryPresent(int timeout_ms) {
|
||||||
// Recreate the presentation FBO if the color attachment was changed
|
// Recreate the presentation FBO if the color attachment was changed
|
||||||
if (frame->color_reloaded) {
|
if (frame->color_reloaded) {
|
||||||
LOG_DEBUG(Render_OpenGL, "Reloading present frame");
|
LOG_DEBUG(Render_OpenGL, "Reloading present frame");
|
||||||
render_window.mailbox->ReloadPresentFrame(frame, layout.width, layout.height);
|
window.mailbox->ReloadPresentFrame(frame, layout.width, layout.height);
|
||||||
}
|
}
|
||||||
glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED);
|
glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED);
|
||||||
// INTEL workaround.
|
// INTEL workaround.
|
||||||
|
|
|
@ -56,7 +56,7 @@ struct PresentationTexture {
|
||||||
|
|
||||||
class RendererOpenGL : public RendererBase {
|
class RendererOpenGL : public RendererBase {
|
||||||
public:
|
public:
|
||||||
explicit RendererOpenGL(Frontend::EmuWindow& window);
|
explicit RendererOpenGL(Frontend::EmuWindow& window, Frontend::EmuWindow* secondary_window);
|
||||||
~RendererOpenGL() override;
|
~RendererOpenGL() override;
|
||||||
|
|
||||||
/// Initialize the renderer
|
/// Initialize the renderer
|
||||||
|
@ -70,7 +70,7 @@ public:
|
||||||
|
|
||||||
/// Draws the latest frame from texture mailbox to the currently bound draw framebuffer in this
|
/// Draws the latest frame from texture mailbox to the currently bound draw framebuffer in this
|
||||||
/// context
|
/// context
|
||||||
void TryPresent(int timeout_ms) override;
|
void TryPresent(int timeout_ms, bool is_secondary) override;
|
||||||
|
|
||||||
/// Prepares for video dumping (e.g. create necessary buffers, etc)
|
/// Prepares for video dumping (e.g. create necessary buffers, etc)
|
||||||
void PrepareVideoDumping() override;
|
void PrepareVideoDumping() override;
|
||||||
|
|
|
@ -39,13 +39,14 @@ Layout::FramebufferLayout g_screenshot_framebuffer_layout;
|
||||||
Memory::MemorySystem* g_memory;
|
Memory::MemorySystem* g_memory;
|
||||||
|
|
||||||
/// Initialize the video core
|
/// Initialize the video core
|
||||||
ResultStatus Init(Frontend::EmuWindow& emu_window, Memory::MemorySystem& memory) {
|
ResultStatus Init(Frontend::EmuWindow& emu_window, Frontend::EmuWindow* secondary_window,
|
||||||
|
Memory::MemorySystem& memory) {
|
||||||
g_memory = &memory;
|
g_memory = &memory;
|
||||||
Pica::Init();
|
Pica::Init();
|
||||||
|
|
||||||
OpenGL::GLES = Settings::values.use_gles;
|
OpenGL::GLES = Settings::values.use_gles;
|
||||||
|
|
||||||
g_renderer = std::make_unique<OpenGL::RendererOpenGL>(emu_window);
|
g_renderer = std::make_unique<OpenGL::RendererOpenGL>(emu_window, secondary_window);
|
||||||
ResultStatus result = g_renderer->Init();
|
ResultStatus result = g_renderer->Init();
|
||||||
|
|
||||||
if (result != ResultStatus::Success) {
|
if (result != ResultStatus::Success) {
|
||||||
|
|
|
@ -54,7 +54,8 @@ enum class ResultStatus {
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Initialize the video core
|
/// Initialize the video core
|
||||||
ResultStatus Init(Frontend::EmuWindow& emu_window, Memory::MemorySystem& memory);
|
ResultStatus Init(Frontend::EmuWindow& emu_window, Frontend::EmuWindow* secondary_window,
|
||||||
|
Memory::MemorySystem& memory);
|
||||||
|
|
||||||
/// Shutdown the video core
|
/// Shutdown the video core
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
Reference in New Issue