Frontend: Add fullscreen UI implementation

This commit is contained in:
Connor McLaughlin 2022-05-15 18:20:21 +10:00 committed by refractionpcsx2
parent 5b9d197b7d
commit 05bed05afe
27 changed files with 7463 additions and 76 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -15,6 +15,8 @@
#include "PrecompiledHeader.h"
#include "pcsx2/SysForwardDefs.h"
#include "AboutDialog.h"
#include "QtHost.h"
#include "QtUtils.h"
@ -52,25 +54,25 @@ AboutDialog::~AboutDialog() = default;
QString AboutDialog::getWebsiteUrl()
{
return QStringLiteral("https://pcsx2.net/");
return QString::fromUtf8(PCSX2_WEBSITE_URL);
}
QString AboutDialog::getSupportForumsUrl()
{
return QStringLiteral("https://forums.pcsx2.net/");
return QString::fromUtf8(PCSX2_FORUMS_URL);
}
QString AboutDialog::getGitHubRepositoryUrl()
{
return QStringLiteral("https://github.com/PCSX2/pcsx2");
return QString::fromUtf8(PCSX2_GITHUB_URL);
}
QString AboutDialog::getLicenseUrl()
{
return QStringLiteral("https://github.com/PCSX2/pcsx2/blob/master/pcsx2/Docs/License.txt");
return QString::fromUtf8(PCSX2_LICENSE_URL);
}
QString AboutDialog::getDiscordServerUrl()
{
return QStringLiteral("https://discord.com/invite/TCz3t9k");
return QString::fromUtf8(PCSX2_DISCORD_URL);
}

View File

@ -183,6 +183,24 @@ void DisplayWidget::updateCursor(bool master_enable)
unsetCursor();
}
void DisplayWidget::handleCloseEvent(QCloseEvent* event)
{
// Closing the separate widget will either cancel the close, or trigger shutdown.
// In the latter case, it's going to destroy us, so don't let Qt do it first.
if (QtHost::IsVMValid())
{
QMetaObject::invokeMethod(g_main_window, "requestShutdown", Q_ARG(bool, true),
Q_ARG(bool, true), Q_ARG(bool, false));
}
else
{
QMetaObject::invokeMethod(g_main_window, &MainWindow::requestExit);
}
// Cancel the event from closing the window.
event->ignore();
}
void DisplayWidget::updateCenterPos()
{
#ifdef _WIN32
@ -330,6 +348,7 @@ bool DisplayWidget::event(QEvent* event)
// don't toggle fullscreen when we're bound.. that wouldn't end well.
if (event->type() == QEvent::MouseButtonDblClick &&
static_cast<const QMouseEvent*>(event)->button() == Qt::LeftButton &&
QtHost::IsVMValid() && !QtHost::IsVMPaused() &&
!InputManager::HasAnyBindingsForKey(InputManager::MakePointerButtonKey(0, 0)) &&
Host::GetBoolSettingValue("UI", "DoubleClickTogglesFullscreen", true))
{
@ -384,10 +403,7 @@ bool DisplayWidget::event(QEvent* event)
case QEvent::Close:
{
// Closing the separate widget will either cancel the close, or trigger shutdown.
// In the latter case, it's going to destroy us, so don't let Qt do it first.
QMetaObject::invokeMethod(g_main_window, "requestShutdown", Q_ARG(bool, true), Q_ARG(bool, true), Q_ARG(bool, false));
event->ignore();
handleCloseEvent(static_cast<QCloseEvent*>(event));
return true;
}
@ -454,12 +470,9 @@ DisplayWidget* DisplayContainer::removeDisplayWidget()
bool DisplayContainer::event(QEvent* event)
{
if (event->type() == QEvent::Close)
if (event->type() == QEvent::Close && m_display_widget)
{
// Closing the separate widget will either cancel the close, or trigger shutdown.
// In the latter case, it's going to destroy us, so don't let Qt do it first.
QMetaObject::invokeMethod(g_main_window, "requestShutdown", Q_ARG(bool, true), Q_ARG(bool, true), Q_ARG(bool, false));
event->ignore();
m_display_widget->handleCloseEvent(static_cast<QCloseEvent*>(event));
return true;
}

View File

@ -20,6 +20,8 @@
#include <optional>
#include <vector>
class QCloseEvent;
class DisplayWidget final : public QWidget
{
Q_OBJECT
@ -41,6 +43,8 @@ public:
void updateRelativeMode(bool master_enable);
void updateCursor(bool master_enable);
void handleCloseEvent(QCloseEvent* event);
Q_SIGNALS:
void windowResizedEvent(int width, int height, float scale);
void windowRestoredEvent();

View File

@ -30,6 +30,7 @@
#include "pcsx2/Counters.h"
#include "pcsx2/Frontend/InputManager.h"
#include "pcsx2/Frontend/ImGuiManager.h"
#include "pcsx2/Frontend/FullscreenUI.h"
#include "pcsx2/GS.h"
#include "pcsx2/GS/GS.h"
#include "pcsx2/GSDumpReplayer.h"
@ -87,6 +88,9 @@ void EmuThread::stopInThread()
if (VMManager::HasValidVM())
destroyVM();
if (m_run_fullscreen_ui)
stopFullscreenUI();
m_event_loop->quit();
m_shutdown_flag.store(true);
}
@ -126,6 +130,53 @@ bool EmuThread::confirmMessage(const QString& title, const QString& message)
return result;
}
void EmuThread::startFullscreenUI(bool fullscreen)
{
if (!isOnEmuThread())
{
QMetaObject::invokeMethod(this, "startFullscreenUI", Qt::QueuedConnection, Q_ARG(bool, fullscreen));
return;
}
if (VMManager::HasValidVM())
return;
m_run_fullscreen_ui = true;
if (fullscreen)
m_is_fullscreen = true;
if (!GetMTGS().WaitForOpen())
{
m_run_fullscreen_ui = false;
return;
}
// poll more frequently so we don't lose events
stopBackgroundControllerPollTimer();
startBackgroundControllerPollTimer();
}
void EmuThread::stopFullscreenUI()
{
if (!isOnEmuThread())
{
QMetaObject::invokeMethod(this, &EmuThread::stopFullscreenUI, Qt::QueuedConnection);
// wait until the host display is gone
while (s_host_display)
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1);
return;
}
if (!s_host_display)
return;
pxAssertRel(!VMManager::HasValidVM(), "VM is not valid at FSUI shutdown time");
m_run_fullscreen_ui = false;
GetMTGS().WaitForClose();
}
void EmuThread::startVM(std::shared_ptr<VMBootParameters> boot_params)
{
if (!isOnEmuThread())
@ -136,15 +187,16 @@ void EmuThread::startVM(std::shared_ptr<VMBootParameters> boot_params)
}
pxAssertRel(!VMManager::HasValidVM(), "VM is shut down");
loadOurSettings();
// Only initialize fullscreen/render-to-main when we're not running big picture.
if (!m_run_fullscreen_ui)
loadOurInitialSettings();
if (boot_params->fullscreen.has_value())
m_is_fullscreen = boot_params->fullscreen.value();
emit onVMStarting();
// create the display, this may take a while...
m_is_fullscreen = boot_params->fullscreen.value_or(Host::GetBaseBoolSettingValue("UI", "StartFullscreen", false));
m_is_rendering_to_main = shouldRenderToMain();
m_is_surfaceless = false;
m_save_state_on_shutdown = false;
if (!VMManager::Initialize(*boot_params))
return;
@ -265,6 +317,7 @@ void EmuThread::saveStateToSlot(qint32 slot)
void EmuThread::run()
{
Threading::SetNameOfCurrentThread("EmuThread");
PerformanceMetrics::SetCPUThread(Threading::ThreadHandle::GetForCallingThread());
m_event_loop = new QEventLoop();
m_started_semaphore.release();
@ -273,8 +326,13 @@ void EmuThread::run()
if (!VMManager::Internal::InitializeGlobals() || !VMManager::Internal::InitializeMemory())
pxFailRel("Failed to allocate memory map");
// we need input sources ready for binding
reloadInputSources();
// We want settings loaded so we choose the correct renderer for big picture mode.
// This also sorts out input sources.
loadOurSettings();
loadOurInitialSettings();
VMManager::LoadSettings();
// Start background polling because the VM won't do it for us.
createBackgroundControllerPollTimer();
startBackgroundControllerPollTimer();
connectSignals();
@ -362,7 +420,9 @@ void EmuThread::startBackgroundControllerPollTimer()
if (m_background_controller_polling_timer->isActive())
return;
m_background_controller_polling_timer->start(BACKGROUND_CONTROLLER_POLLING_INTERVAL);
m_background_controller_polling_timer->start(FullscreenUI::IsInitialized() ?
FULLSCREEN_UI_CONTROLLER_POLLING_INTERVAL :
BACKGROUND_CONTROLLER_POLLING_INTERVAL);
}
void EmuThread::stopBackgroundControllerPollTimer()
@ -397,7 +457,7 @@ void EmuThread::setFullscreen(bool fullscreen)
return;
}
if (!VMManager::HasValidVM() || m_is_fullscreen == fullscreen)
if (!GetMTGS().IsOpen() || m_is_fullscreen == fullscreen)
return;
// This will call back to us on the MTGS thread.
@ -417,9 +477,13 @@ void EmuThread::setSurfaceless(bool surfaceless)
return;
}
if (!VMManager::HasValidVM() || m_is_surfaceless == surfaceless)
if (!GetMTGS().IsOpen() || m_is_surfaceless == surfaceless)
return;
// If we went surfaceless and were running the fullscreen UI, stop MTGS running idle.
// Otherwise, we'll keep trying to present to nothing.
GetMTGS().SetRunIdle(!surfaceless && m_run_fullscreen_ui);
// This will call back to us on the MTGS thread.
m_is_surfaceless = surfaceless;
GetMTGS().UpdateDisplayWindow();
@ -476,11 +540,19 @@ void EmuThread::connectSignals()
connect(qApp, &QGuiApplication::applicationStateChanged, this, &EmuThread::onApplicationStateChanged);
}
void EmuThread::loadOurInitialSettings()
{
m_is_fullscreen = Host::GetBaseBoolSettingValue("UI", "StartFullscreen", false);
m_is_rendering_to_main = shouldRenderToMain();
m_is_surfaceless = false;
m_save_state_on_shutdown = false;
}
void EmuThread::checkForSettingChanges()
{
QMetaObject::invokeMethod(g_main_window, &MainWindow::checkForSettingChanges, Qt::QueuedConnection);
if (VMManager::HasValidVM())
if (s_host_display)
{
const bool render_to_main = shouldRenderToMain();
if (!m_is_fullscreen && m_is_rendering_to_main != render_to_main)
@ -772,6 +844,14 @@ HostDisplay* EmuThread::acquireHostDisplay(HostDisplay::RenderAPI api)
Console.WriteLn(Color_StrongGreen, "%s Graphics Driver Info:", HostDisplay::RenderAPIToString(s_host_display->GetRenderAPI()));
Console.Indent().WriteLn(s_host_display->GetDriverInfo());
if (m_run_fullscreen_ui && !FullscreenUI::Initialize())
{
Console.Error("Failed to initialize fullscreen UI");
releaseHostDisplay();
m_run_fullscreen_ui = false;
return nullptr;
}
return s_host_display.get();
}
@ -816,6 +896,7 @@ void Host::EndPresentFrame()
if (GSDumpReplayer::IsReplayingDump())
GSDumpReplayer::RenderUI();
FullscreenUI::Render();
ImGuiManager::RenderOSD();
s_host_display->EndPresent();
ImGuiManager::NewFrame();
@ -1036,14 +1117,15 @@ void Host::RequestExit(bool save_state_if_running)
QMetaObject::invokeMethod(g_main_window, "requestExit", Qt::QueuedConnection);
}
void Host::RequestVMShutdown(bool allow_confirm, bool allow_save_state)
void Host::RequestVMShutdown(bool allow_confirm, bool allow_save_state, bool default_save_state)
{
if (!VMManager::HasValidVM())
return;
// Run it on the host thread, that way we get the confirm prompt (if enabled).
QMetaObject::invokeMethod(g_main_window, "requestShutdown", Qt::QueuedConnection,
Q_ARG(bool, allow_confirm), Q_ARG(bool, allow_save_state), Q_ARG(bool, false));
Q_ARG(bool, allow_confirm), Q_ARG(bool, allow_save_state),
Q_ARG(bool, default_save_state), Q_ARG(bool, false));
}
bool Host::IsFullscreen()

View File

@ -47,6 +47,7 @@ public:
__fi bool isFullscreen() const { return m_is_fullscreen; }
__fi bool isRenderingToMain() const { return m_is_rendering_to_main; }
__fi bool isSurfaceless() const { return m_is_surfaceless; }
__fi bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; }
bool isOnEmuThread() const;
@ -62,6 +63,8 @@ public:
public Q_SLOTS:
bool confirmMessage(const QString& title, const QString& message);
void startFullscreenUI(bool fullscreen);
void stopFullscreenUI();
void startVM(std::shared_ptr<VMBootParameters> boot_params);
void resetVM();
void setVMPaused(bool paused);
@ -133,8 +136,11 @@ protected:
void run();
private:
static constexpr u32 BACKGROUND_CONTROLLER_POLLING_INTERVAL =
100; /// Interval at which the controllers are polled when the system is not active.
/// Interval at which the controllers are polled when the system is not active.
static constexpr u32 BACKGROUND_CONTROLLER_POLLING_INTERVAL = 100;
/// Poll at half the vsync rate for FSUI to reduce the chance of getting a press+release in the same frame.
static constexpr u32 FULLSCREEN_UI_CONTROLLER_POLLING_INTERVAL = 8;
void destroyVM();
void executeVM();
@ -143,8 +149,9 @@ private:
void createBackgroundControllerPollTimer();
void destroyBackgroundControllerPollTimer();
void loadOurSettings();
void connectSignals();
void loadOurSettings();
void loadOurInitialSettings();
private Q_SLOTS:
void stopInThread();
@ -162,6 +169,7 @@ private:
std::atomic_bool m_shutdown_flag{false};
bool m_verbose_status = false;
bool m_run_fullscreen_ui = false;
bool m_is_rendering_to_main = false;
bool m_is_fullscreen = false;
bool m_is_surfaceless = false;

View File

@ -251,8 +251,8 @@ void MainWindow::connectSignals()
connect(m_ui.actionRemoveDisc, &QAction::triggered, this, &MainWindow::onRemoveDiscActionTriggered);
connect(m_ui.menuChangeDisc, &QMenu::aboutToShow, this, &MainWindow::onChangeDiscMenuAboutToShow);
connect(m_ui.menuChangeDisc, &QMenu::aboutToHide, this, &MainWindow::onChangeDiscMenuAboutToHide);
connect(m_ui.actionPowerOff, &QAction::triggered, this, [this]() { requestShutdown(true, true); });
connect(m_ui.actionPowerOffWithoutSaving, &QAction::triggered, this, [this]() { requestShutdown(false, false); });
connect(m_ui.actionPowerOff, &QAction::triggered, this, [this]() { requestShutdown(true, true, EmuConfig.SaveStateOnShutdown); });
connect(m_ui.actionPowerOffWithoutSaving, &QAction::triggered, this, [this]() { requestShutdown(false, false, false); });
connect(m_ui.actionLoadState, &QAction::triggered, this, [this]() { m_ui.menuLoadState->exec(QCursor::pos()); });
connect(m_ui.actionSaveState, &QAction::triggered, this, [this]() { m_ui.menuSaveState->exec(QCursor::pos()); });
connect(m_ui.actionExit, &QAction::triggered, this, &MainWindow::close);
@ -354,6 +354,8 @@ void MainWindow::connectSignals()
void MainWindow::connectVMThreadSignals(EmuThread* thread)
{
connect(m_ui.actionStartFullscreenUI, &QAction::triggered, thread, &EmuThread::startFullscreenUI);
connect(m_ui.actionStartFullscreenUI2, &QAction::triggered, thread, &EmuThread::startFullscreenUI);
connect(thread, &EmuThread::messageConfirmed, this, &MainWindow::confirmMessage, Qt::BlockingQueuedConnection);
connect(thread, &EmuThread::onCreateDisplayRequested, this, &MainWindow::createDisplay, Qt::BlockingQueuedConnection);
connect(thread, &EmuThread::onUpdateDisplayRequested, this, &MainWindow::updateDisplay, Qt::BlockingQueuedConnection);
@ -387,7 +389,7 @@ void MainWindow::connectVMThreadSignals(EmuThread* thread)
void MainWindow::recreate()
{
if (s_vm_valid)
requestShutdown(false, true, true);
requestShutdown(false, true, EmuConfig.SaveStateOnShutdown);
close();
g_main_window = nullptr;
@ -950,7 +952,7 @@ void MainWindow::switchToGameListView()
return;
}
if (s_vm_valid)
if (m_display_created)
{
m_was_paused_on_surface_loss = s_vm_paused;
if (!s_vm_paused)
@ -965,7 +967,7 @@ void MainWindow::switchToGameListView()
void MainWindow::switchToEmulationView()
{
if (!s_vm_valid || !isShowingGameList())
if (!m_display_created || !isShowingGameList())
return;
// we're no longer surfaceless! this will call back to UpdateDisplay(), which will swap the widget out.
@ -1014,14 +1016,14 @@ void MainWindow::runOnUIThread(const std::function<void()>& func)
func();
}
bool MainWindow::requestShutdown(bool allow_confirm /* = true */, bool allow_save_to_state /* = true */, bool block_until_done /* = false */)
bool MainWindow::requestShutdown(bool allow_confirm /* = true */, bool allow_save_to_state /* = true */, bool default_save_to_state /* = true */, bool block_until_done /* = false */)
{
if (!s_vm_valid)
return true;
// If we don't have a crc, we can't save state.
allow_save_to_state &= (m_current_game_crc != 0);
bool save_state = allow_save_to_state && EmuConfig.SaveStateOnShutdown;
bool save_state = allow_save_to_state && default_save_to_state;
// Only confirm on UI thread because we need to display a msgbox.
if (!m_is_closing && allow_confirm && !GSDumpReplayer::IsReplayingDump() && Host::GetBaseBoolSettingValue("UI", "ConfirmShutdown", true))
@ -1081,7 +1083,7 @@ bool MainWindow::requestShutdown(bool allow_confirm /* = true */, bool allow_sav
void MainWindow::requestExit()
{
// this is block, because otherwise closeEvent() will also prompt
if (!requestShutdown(true, true, true))
if (!requestShutdown(true, true, EmuConfig.SaveStateOnShutdown, true))
return;
// We could use close here, but if we're not visible (e.g. quitting from fullscreen), closing the window
@ -1334,7 +1336,7 @@ void MainWindow::onViewGameGridActionTriggered()
void MainWindow::onViewSystemDisplayTriggered()
{
if (s_vm_valid)
if (m_display_created)
switchToEmulationView();
}
@ -1643,7 +1645,7 @@ void MainWindow::showEvent(QShowEvent* event)
void MainWindow::closeEvent(QCloseEvent* event)
{
if (!requestShutdown(true, true, true))
if (!requestShutdown(true, true, EmuConfig.SaveStateOnShutdown, true))
{
event->ignore();
return;
@ -1742,6 +1744,8 @@ DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main)
return nullptr;
}
m_display_created = true;
if (is_exclusive_fullscreen)
setDisplayFullscreen(fullscreen_mode);
@ -1750,6 +1754,8 @@ DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main)
m_ui.actionViewSystemDisplay->setEnabled(true);
m_ui.actionFullscreen->setEnabled(true);
m_ui.actionStartFullscreenUI->setEnabled(false);
m_ui.actionStartFullscreenUI2->setEnabled(false);
m_display_widget->setShouldHideCursor(shouldHideMouseCursor());
m_display_widget->updateRelativeMode(s_vm_valid && !s_vm_paused);
@ -1940,9 +1946,12 @@ void MainWindow::destroyDisplay()
{
// Now we can safely destroy the display window.
destroyDisplayWidget(true);
m_display_created = false;
m_ui.actionViewSystemDisplay->setEnabled(false);
m_ui.actionFullscreen->setEnabled(false);
m_ui.actionStartFullscreenUI->setEnabled(true);
m_ui.actionStartFullscreenUI2->setEnabled(true);
}
void MainWindow::destroyDisplayWidget(bool show_game_list)

View File

@ -104,7 +104,7 @@ public Q_SLOTS:
void reportError(const QString& title, const QString& message);
bool confirmMessage(const QString& title, const QString& message);
void runOnUIThread(const std::function<void()>& func);
bool requestShutdown(bool allow_confirm = true, bool allow_save_to_state = true, bool block_until_done = false);
bool requestShutdown(bool allow_confirm = true, bool allow_save_to_state = true, bool default_save_to_state = true, bool block_until_done = false);
void requestExit();
void checkForSettingChanges();
@ -258,6 +258,7 @@ private:
QString m_current_game_name;
quint32 m_current_game_crc;
bool m_display_created = false;
bool m_save_states_invalidated = false;
bool m_was_paused_on_surface_loss = false;
bool m_was_disc_change_request = false;

View File

@ -66,6 +66,7 @@
<addaction name="actionStartFile"/>
<addaction name="actionStartDisc"/>
<addaction name="actionStartBios"/>
<addaction name="actionStartFullscreenUI"/>
<addaction name="separator"/>
<addaction name="actionPowerOff"/>
<addaction name="actionPowerOffWithoutSaving"/>
@ -225,6 +226,7 @@
<addaction name="actionStartFile"/>
<addaction name="actionStartDisc"/>
<addaction name="actionStartBios"/>
<addaction name="actionStartFullscreenUI2"/>
<addaction name="separator"/>
<addaction name="actionPowerOff"/>
<addaction name="actionReset"/>
@ -829,6 +831,24 @@
<string>Enable Log Timestamps</string>
</property>
</action>
<action name="actionStartFullscreenUI">
<property name="icon">
<iconset theme="tv-2-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Start Big Picture Mode</string>
</property>
</action>
<action name="actionStartFullscreenUI2">
<property name="icon">
<iconset theme="tv-2-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Big Picture</string>
</property>
</action>
</widget>
<resources>
<include location="resources/resources.qrc"/>

View File

@ -82,6 +82,8 @@ static std::unique_ptr<QTimer> s_settings_save_timer;
static std::unique_ptr<INISettingsInterface> s_base_settings_interface;
static bool s_batch_mode = false;
static bool s_nogui_mode = false;
static bool s_start_fullscreen_ui = false;
static bool s_start_fullscreen_ui_fullscreen = false;
//////////////////////////////////////////////////////////////////////////
// Initialization/Shutdown
@ -617,6 +619,7 @@ bool QtHost::ParseCommandLineOptions(int argc, char* argv[], std::shared_ptr<VMB
else if (CHECK_ARG("-fullscreen"))
{
AutoBoot(autoboot)->fullscreen = true;
s_start_fullscreen_ui_fullscreen = true;
continue;
}
else if (CHECK_ARG("-nofullscreen"))
@ -629,6 +632,11 @@ bool QtHost::ParseCommandLineOptions(int argc, char* argv[], std::shared_ptr<VMB
Host::InitializeEarlyConsole();
continue;
}
else if (CHECK_ARG("-bigpicture"))
{
s_start_fullscreen_ui = true;
continue;
}
else if (CHECK_ARG("--"))
{
no_more_args = true;
@ -662,7 +670,7 @@ bool QtHost::ParseCommandLineOptions(int argc, char* argv[], std::shared_ptr<VMB
// if we don't have autoboot, we definitely don't want batch mode (because that'll skip
// scanning the game list).
if (s_batch_mode && !autoboot)
if (s_batch_mode && !s_start_fullscreen_ui && !autoboot)
{
QMessageBox::critical(nullptr, QStringLiteral("Error"), s_nogui_mode ?
QStringLiteral("Cannot use no-gui mode, because no boot filename was specified.") :
@ -750,10 +758,14 @@ int main(int argc, char* argv[])
if (!s_nogui_mode)
main_window->show();
// Initialize big picture mode if requested.
if (s_start_fullscreen_ui)
g_emu_thread->startFullscreenUI(s_start_fullscreen_ui_fullscreen);
// Skip the update check if we're booting a game directly.
if (autoboot)
g_emu_thread->startVM(std::move(autoboot));
else
else if (!s_nogui_mode)
main_window->startupUpdateCheck();
// This doesn't return until we exit.

View File

@ -1063,7 +1063,9 @@ endif()
if(PCSX2_CORE)
list(APPEND pcsx2FrontendSources
Frontend/FullscreenUI.cpp
Frontend/GameList.cpp
Frontend/ImGuiFullscreen.cpp
Frontend/INISettingsInterface.cpp
Frontend/InputManager.cpp
Frontend/InputSource.cpp
@ -1074,7 +1076,9 @@ if(PCSX2_CORE)
VMManager.cpp
)
list(APPEND pcsx2FrontendHeaders
Frontend/FullscreenUI.h
Frontend/GameList.h
Frontend/ImGuiFullscreen.h
Frontend/INISettingsInterface.h
Frontend/InputManager.h
Frontend/InputSource.h

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,75 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "common/Pcsx2Defs.h"
#include "common/ProgressCallback.h"
#include <string>
#include <memory>
class HostDisplayTexture;
namespace FullscreenUI
{
bool Initialize();
bool IsInitialized();
bool HasActiveWindow();
void OnVMStarted();
void OnVMPaused();
void OnVMResumed();
void OnVMDestroyed();
void OnRunningGameChanged(std::string path, std::string serial, std::string title, u32 crc);
void OpenPauseMenu();
void Shutdown();
void Render();
// Returns true if the message has been dismissed.
bool DrawErrorWindow(const char* message);
bool DrawConfirmWindow(const char* message, bool* result);
class ProgressCallback final : public BaseProgressCallback
{
public:
ProgressCallback(std::string name);
~ProgressCallback() override;
void PushState() override;
void PopState() override;
void SetCancellable(bool cancellable) override;
void SetTitle(const char* title) override;
void SetStatusText(const char* text) override;
void SetProgressRange(u32 range) override;
void SetProgressValue(u32 value) override;
void DisplayError(const char* message) override;
void DisplayWarning(const char* message) override;
void DisplayInformation(const char* message) override;
void DisplayDebugMessage(const char* message) override;
void ModalError(const char* message) override;
bool ModalConfirmation(const char* message) override;
void ModalInformation(const char* message) override;
void SetCancelled();
private:
void Redraw(bool force);
std::string m_name;
int m_last_progress_percent = -1;
};
} // namespace FullscreenUI

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,239 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "common/Pcsx2Defs.h"
#include "imgui.h"
#include "imgui_internal.h"
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <vector>
class HostDisplayTexture;
namespace ImGuiFullscreen
{
#define HEX_TO_IMVEC4(hex, alpha) \
ImVec4(static_cast<float>((hex >> 16) & 0xFFu) / 255.0f, static_cast<float>((hex >> 8) & 0xFFu) / 255.0f, \
static_cast<float>(hex & 0xFFu) / 255.0f, static_cast<float>(alpha) / 255.0f)
static constexpr float LAYOUT_SCREEN_WIDTH = 1280.0f;
static constexpr float LAYOUT_SCREEN_HEIGHT = 720.0f;
static constexpr float LAYOUT_LARGE_FONT_SIZE = 26.0f;
static constexpr float LAYOUT_MEDIUM_FONT_SIZE = 16.0f;
static constexpr float LAYOUT_SMALL_FONT_SIZE = 10.0f;
static constexpr float LAYOUT_MENU_BUTTON_HEIGHT = 50.0f;
static constexpr float LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY = 26.0f;
static constexpr float LAYOUT_MENU_BUTTON_X_PADDING = 15.0f;
static constexpr float LAYOUT_MENU_BUTTON_Y_PADDING = 10.0f;
extern ImFont* g_standard_font;
extern ImFont* g_medium_font;
extern ImFont* g_large_font;
extern float g_layout_scale;
extern float g_layout_padding_left;
extern float g_layout_padding_top;
extern ImVec4 UIBackgroundColor;
extern ImVec4 UIBackgroundTextColor;
extern ImVec4 UIBackgroundLineColor;
extern ImVec4 UIBackgroundHighlightColor;
extern ImVec4 UIDisabledColor;
extern ImVec4 UIPrimaryColor;
extern ImVec4 UIPrimaryLightColor;
extern ImVec4 UIPrimaryDarkColor;
extern ImVec4 UIPrimaryTextColor;
extern ImVec4 UITextHighlightColor;
extern ImVec4 UIPrimaryLineColor;
extern ImVec4 UISecondaryColor;
extern ImVec4 UISecondaryLightColor;
extern ImVec4 UISecondaryDarkColor;
extern ImVec4 UISecondaryTextColor;
static __fi float DPIScale(float v) { return ImGui::GetIO().DisplayFramebufferScale.x * v; }
static __fi float DPIScale(int v) { return ImGui::GetIO().DisplayFramebufferScale.x * static_cast<float>(v); }
static __fi ImVec2 DPIScale(const ImVec2& v)
{
const ImVec2& fbs = ImGui::GetIO().DisplayFramebufferScale;
return ImVec2(v.x * fbs.x, v.y * fbs.y);
}
static __fi float WindowWidthScale(float v) { return ImGui::GetWindowWidth() * v; }
static __fi float WindowHeightScale(float v) { return ImGui::GetWindowHeight() * v; }
static __fi float LayoutScale(float v) { return g_layout_scale * v; }
static __fi ImVec2 LayoutScale(const ImVec2& v) { return ImVec2(v.x * g_layout_scale, v.y * g_layout_scale); }
static __fi ImVec2 LayoutScale(float x, float y) { return ImVec2(x * g_layout_scale, y * g_layout_scale); }
static __fi ImVec2 LayoutScaleAndOffset(float x, float y)
{
return ImVec2(g_layout_padding_left + x * g_layout_scale, g_layout_padding_top + y * g_layout_scale);
}
static __fi ImVec4 ModAlpha(const ImVec4& v, float a)
{
return ImVec4(v.x, v.y, v.z, a);
}
/// Centers an image within the specified bounds, scaling up or down as needed.
ImRect CenterImage(const ImVec2& fit_size, const ImVec2& image_size);
ImRect CenterImage(const ImRect& fit_rect, const ImVec2& image_size);
/// Initializes, setting up any state.
bool Initialize(const char* placeholder_image_path);
void SetTheme();
void SetFonts(ImFont* standard_font, ImFont* medium_font, ImFont* large_font);
bool UpdateLayoutScale();
/// Shuts down, clearing all state.
void Shutdown();
/// Texture cache.
const std::shared_ptr<HostDisplayTexture>& GetPlaceholderTexture();
std::shared_ptr<HostDisplayTexture> LoadTexture(const char* path);
HostDisplayTexture* GetCachedTexture(const char* name);
HostDisplayTexture* GetCachedTextureAsync(const char* name);
bool InvalidateCachedTexture(const std::string& path);
void UploadAsyncTextures();
void BeginLayout();
void EndLayout();
void QueueResetFocus();
bool ResetFocusHere();
bool WantsToCloseMenu();
void ResetCloseMenuIfNeeded();
void PushPrimaryColor();
void PopPrimaryColor();
void PushSecondaryColor();
void PopSecondaryColor();
void DrawWindowTitle(const char* title);
bool BeginFullscreenColumns(const char* title = nullptr);
void EndFullscreenColumns();
bool BeginFullscreenColumnWindow(float start, float end, const char* name, const ImVec4& background = UIBackgroundColor);
void EndFullscreenColumnWindow();
bool BeginFullscreenWindow(float left, float top, float width, float height, const char* name,
const ImVec4& background = HEX_TO_IMVEC4(0x212121, 0xFF), float rounding = 0.0f, float padding = 0.0f, ImGuiWindowFlags flags = 0);
bool BeginFullscreenWindow(const ImVec2& position, const ImVec2& size, const char* name,
const ImVec4& background = HEX_TO_IMVEC4(0x212121, 0xFF), float rounding = 0.0f, float padding = 0.0f, ImGuiWindowFlags flags = 0);
void EndFullscreenWindow();
void BeginMenuButtons(u32 num_items = 0, float y_align = 0.0f, float x_padding = LAYOUT_MENU_BUTTON_X_PADDING,
float y_padding = LAYOUT_MENU_BUTTON_Y_PADDING, float item_height = LAYOUT_MENU_BUTTON_HEIGHT);
void EndMenuButtons();
bool MenuButtonFrame(const char* str_id, bool enabled, float height, bool* visible, bool* hovered, ImVec2* min, ImVec2* max,
ImGuiButtonFlags flags = 0, float hover_alpha = 1.0f);
void MenuHeading(const char* title, bool draw_line = true);
bool MenuHeadingButton(const char* title, const char* value = nullptr, bool enabled = true, bool draw_line = true);
bool ActiveButton(const char* title, bool is_active, bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY,
ImFont* font = g_large_font);
bool MenuButton(const char* title, const char* summary, bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT,
ImFont* font = g_large_font, ImFont* summary_font = g_medium_font);
bool MenuButtonWithValue(const char* title, const char* summary, const char* value, bool enabled = true,
float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, ImFont* summary_font = g_medium_font);
bool MenuImageButton(const char* title, const char* summary, ImTextureID user_texture_id, const ImVec2& image_size, bool enabled = true,
float height = LAYOUT_MENU_BUTTON_HEIGHT, const ImVec2& uv0 = ImVec2(0.0f, 0.0f), const ImVec2& uv1 = ImVec2(1.0f, 1.0f),
ImFont* font = g_large_font, ImFont* summary_font = g_medium_font);
bool FloatingButton(const char* text, float x, float y, float width = -1.0f, float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY,
float anchor_x = 0.0f, float anchor_y = 0.0f, bool enabled = true, ImFont* font = g_large_font, ImVec2* out_position = nullptr);
bool ToggleButton(const char* title, const char* summary, bool* v, bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT,
ImFont* font = g_large_font, ImFont* summary_font = g_medium_font);
bool ThreeWayToggleButton(const char* title, const char* summary, std::optional<bool>* v, bool enabled = true,
float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, ImFont* summary_font = g_medium_font);
bool RangeButton(const char* title, const char* summary, s32* value, s32 min, s32 max, s32 increment, const char* format = "%d",
bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, ImFont* summary_font = g_medium_font);
bool RangeButton(const char* title, const char* summary, float* value, float min, float max, float increment, const char* format = "%f",
bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, ImFont* summary_font = g_medium_font);
bool EnumChoiceButtonImpl(const char* title, const char* summary, s32* value_pointer,
const char* (*to_display_name_function)(s32 value, void* opaque), void* opaque, u32 count, bool enabled, float height, ImFont* font,
ImFont* summary_font);
template <typename DataType, typename CountType>
static __fi bool EnumChoiceButton(const char* title, const char* summary, DataType* value_pointer,
const char* (*to_display_name_function)(DataType value), CountType count, bool enabled = true,
float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, ImFont* summary_font = g_medium_font)
{
s32 value = static_cast<s32>(*value_pointer);
auto to_display_name_wrapper = [](s32 value, void* opaque) -> const char* {
return (*static_cast<decltype(to_display_name_function)*>(opaque))(static_cast<DataType>(value));
};
if (EnumChoiceButtonImpl(title, summary, &value, to_display_name_wrapper, &to_display_name_function, static_cast<u32>(count),
enabled, height, font, summary_font))
{
*value_pointer = static_cast<DataType>(value);
return true;
}
else
{
return false;
}
}
void BeginNavBar(float x_padding = LAYOUT_MENU_BUTTON_X_PADDING, float y_padding = LAYOUT_MENU_BUTTON_Y_PADDING);
void EndNavBar();
void NavTitle(const char* title, float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = g_large_font);
void RightAlignNavButtons(u32 num_items = 0, float item_width = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY,
float item_height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
bool NavButton(const char* title, bool is_active, bool enabled = true, float width = -1.0f,
float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = g_large_font);
using FileSelectorCallback = std::function<void(const std::string& path)>;
using FileSelectorFilters = std::vector<std::string>;
bool IsFileSelectorOpen();
void OpenFileSelector(const char* title, bool select_directory, FileSelectorCallback callback,
FileSelectorFilters filters = FileSelectorFilters(), std::string initial_directory = std::string());
void CloseFileSelector();
using ChoiceDialogCallback = std::function<void(s32 index, const std::string& title, bool checked)>;
using ChoiceDialogOptions = std::vector<std::pair<std::string, bool>>;
bool IsChoiceDialogOpen();
void OpenChoiceDialog(const char* title, bool checkable, ChoiceDialogOptions options, ChoiceDialogCallback callback);
void CloseChoiceDialog();
using InputStringDialogCallback = std::function<void(std::string text)>;
bool IsInputDialogOpen();
void OpenInputStringDialog(
std::string title, std::string message, std::string caption, std::string ok_button_text, InputStringDialogCallback callback);
void CloseInputDialog();
float GetNotificationVerticalPosition();
float GetNotificationVerticalDirection();
void SetNotificationVerticalPosition(float position, float direction);
void OpenBackgroundProgressDialog(const char* str_id, std::string message, s32 min, s32 max, s32 value);
void UpdateBackgroundProgressDialog(const char* str_id, std::string message, s32 min, s32 max, s32 value);
void CloseBackgroundProgressDialog(const char* str_id);
void AddNotification(float duration, std::string title, std::string text, std::string image_path);
void ClearNotifications();
void ShowToast(std::string title, std::string message, float duration = 10.0f);
void ClearToast();
} // namespace ImGuiFullscreen

View File

@ -34,7 +34,6 @@
#include "Config.h"
#include "Counters.h"
#include "Frontend/ImGuiManager.h"
#include "Frontend/InputManager.h"
#include "GS.h"
#include "GS/GS.h"
#include "Host.h"
@ -43,6 +42,10 @@
#include "PerformanceMetrics.h"
#ifdef PCSX2_CORE
#include "Frontend/FullscreenUI.h"
#include "Frontend/ImGuiManager.h"
#include "Frontend/ImGuiFullscreen.h"
#include "Frontend/InputManager.h"
#include "VMManager.h"
#endif
@ -52,7 +55,7 @@ namespace ImGuiManager
static void SetKeyMap();
static bool LoadFontData();
static void UnloadFontData();
static bool AddImGuiFonts();
static bool AddImGuiFonts(bool fullscreen_fonts);
static ImFont* AddTextFont(float size);
static ImFont* AddFixedFont(float size);
static bool AddIconFonts(float size);
@ -66,6 +69,8 @@ static float s_global_scale = 1.0f;
static ImFont* s_standard_font;
static ImFont* s_fixed_font;
static ImFont* s_medium_font;
static ImFont* s_large_font;
static std::vector<u8> s_standard_font_data;
static std::vector<u8> s_fixed_font_data;
@ -111,6 +116,10 @@ bool ImGuiManager::Initialize()
SetKeyMap();
SetStyle();
#ifdef PCSX2_CORE
pxAssertRel(!FullscreenUI::IsInitialized(), "Fullscreen UI is not initialized on ImGui init");
#endif
if (!display->CreateImGuiContext())
{
pxFailRel("Failed to create ImGui device context");
@ -120,7 +129,7 @@ bool ImGuiManager::Initialize()
return false;
}
if (!AddImGuiFonts() || !display->UpdateImGuiFontTexture())
if (!AddImGuiFonts(false) || !display->UpdateImGuiFontTexture())
{
pxFailRel("Failed to create ImGui font text");
display->DestroyImGuiContext();
@ -138,6 +147,10 @@ bool ImGuiManager::Initialize()
void ImGuiManager::Shutdown()
{
#ifdef PCSX2_CORE
FullscreenUI::Shutdown();
#endif
HostDisplay* display = Host::GetHostDisplay();
if (display)
display->DestroyImGuiContext();
@ -146,6 +159,11 @@ void ImGuiManager::Shutdown()
s_standard_font = nullptr;
s_fixed_font = nullptr;
s_medium_font = nullptr;
s_large_font = nullptr;
#ifdef PCSX2_CORE
ImGuiFullscreen::SetFonts(nullptr, nullptr, nullptr);
#endif
UnloadFontData();
}
@ -172,8 +190,13 @@ void ImGuiManager::UpdateScale()
const float window_scale = display ? display->GetWindowScale() : 1.0f;
const float scale = std::max(window_scale * static_cast<float>(EmuConfig.GS.OsdScale / 100.0), 1.0f);
#ifdef PCSX2_CORE
if (scale == s_global_scale && (!HasFullscreenFonts() || !ImGuiFullscreen::UpdateLayoutScale()))
return;
#else
if (scale == s_global_scale)
return;
#endif
// This is assumed to be called mid-frame.
ImGui::EndFrame();
@ -185,7 +208,7 @@ void ImGuiManager::UpdateScale()
SetStyle();
ImGui::GetStyle().ScaleAllSizes(scale);
if (!AddImGuiFonts())
if (!AddImGuiFonts(HasFullscreenFonts()))
pxFailRel("Failed to create ImGui font text");
if (!display->UpdateImGuiFontTexture())
@ -393,7 +416,7 @@ ImFont* ImGuiManager::AddFixedFont(float size)
bool ImGuiManager::AddIconFonts(float size)
{
static const ImWchar range_fa[] = {ICON_MIN_FA, ICON_MAX_FA, 0};
static constexpr ImWchar range_fa[] = { 0xf001,0xf002,0xf005,0xf005,0xf00c,0xf00e,0xf011,0xf011,0xf013,0xf013,0xf017,0xf017,0xf019,0xf019,0xf021,0xf021,0xf025,0xf025,0xf028,0xf028,0xf030,0xf030,0xf03a,0xf03a,0xf03d,0xf03d,0xf04a,0xf04c,0xf04e,0xf04e,0xf050,0xf050,0xf052,0xf052,0xf059,0xf059,0xf05e,0xf05e,0xf065,0xf065,0xf067,0xf067,0xf071,0xf071,0xf07b,0xf07c,0xf085,0xf085,0xf091,0xf091,0xf0a0,0xf0a0,0xf0ac,0xf0ad,0xf0c5,0xf0c5,0xf0c7,0xf0c9,0xf0d0,0xf0d0,0xf0e2,0xf0e2,0xf0eb,0xf0eb,0xf0f3,0xf0f3,0xf0fe,0xf0fe,0xf110,0xf110,0xf11b,0xf11c,0xf121,0xf121,0xf140,0xf140,0xf144,0xf144,0xf14a,0xf14a,0xf15b,0xf15b,0xf188,0xf188,0xf192,0xf192,0xf1c9,0xf1c9,0xf1dd,0xf1de,0xf1e6,0xf1e6,0xf1ea,0xf1eb,0xf1f8,0xf1f8,0xf1fc,0xf1fc,0xf242,0xf242,0xf245,0xf245,0xf26c,0xf26c,0xf2d0,0xf2d0,0xf2db,0xf2db,0xf2f5,0xf2f5,0xf410,0xf410,0xf466,0xf466,0xf51f,0xf51f,0xf543,0xf543,0xf545,0xf545,0xf547,0xf548,0xf552,0xf552,0xf65d,0xf65e,0xf756,0xf756,0xf7c2,0xf7c2,0xf815,0xf815,0xf818,0xf818,0xf8cc,0xf8cc,0x0,0x0 };
ImFontConfig cfg;
cfg.MergeMode = true;
@ -406,7 +429,7 @@ bool ImGuiManager::AddIconFonts(float size)
s_icon_font_data.data(), static_cast<int>(s_icon_font_data.size()), size * 0.75f, &cfg, range_fa) != nullptr);
}
bool ImGuiManager::AddImGuiFonts()
bool ImGuiManager::AddImGuiFonts(bool fullscreen_fonts)
{
const float standard_font_size = std::ceil(15.0f * s_global_scale);
@ -421,9 +444,56 @@ bool ImGuiManager::AddImGuiFonts()
if (!s_fixed_font)
return false;
#ifdef PCSX2_CORE
if (fullscreen_fonts)
{
const float medium_font_size = std::ceil(ImGuiFullscreen::LayoutScale(ImGuiFullscreen::LAYOUT_MEDIUM_FONT_SIZE));
s_medium_font = AddTextFont(medium_font_size);
if (!s_medium_font || !AddIconFonts(medium_font_size))
return false;
const float large_font_size = std::ceil(ImGuiFullscreen::LayoutScale(ImGuiFullscreen::LAYOUT_LARGE_FONT_SIZE));
s_large_font = AddTextFont(large_font_size);
if (!s_large_font || !AddIconFonts(large_font_size))
return false;
}
else
{
s_medium_font = nullptr;
s_large_font = nullptr;
}
ImGuiFullscreen::SetFonts(s_standard_font, s_medium_font, s_large_font);
#endif
return io.Fonts->Build();
}
bool ImGuiManager::AddFullscreenFontsIfMissing()
{
if (HasFullscreenFonts())
return true;
// can't do this in the middle of a frame
ImGui::EndFrame();
if (!AddImGuiFonts(true))
{
Console.Error("Failed to lazily allocate fullscreen fonts.");
AddImGuiFonts(false);
}
Host::GetHostDisplay()->UpdateImGuiFontTexture();
NewFrame();
return HasFullscreenFonts();
}
bool ImGuiManager::HasFullscreenFonts()
{
return (s_medium_font && s_large_font);
}
struct OSDMessage
{
std::string key;
@ -767,7 +837,13 @@ void ImGuiManager::RenderOSD()
// acquire for IO.MousePos.
std::atomic_thread_fence(std::memory_order_acquire);
#ifdef PCSX2_CORE
// Don't draw OSD when we're just running big picture.
if (VMManager::HasValidVM())
DrawPerformanceOverlay();
#else
DrawPerformanceOverlay();
#endif
AcquirePendingOSDMessages();
DrawOSDMessages();
@ -788,6 +864,18 @@ ImFont* ImGuiManager::GetFixedFont()
return s_fixed_font;
}
ImFont* ImGuiManager::GetMediumFont()
{
AddFullscreenFontsIfMissing();
return s_medium_font;
}
ImFont* ImGuiManager::GetLargeFont()
{
AddFullscreenFontsIfMissing();
return s_large_font;
}
#ifdef PCSX2_CORE
bool ImGuiManager::WantsTextInput()
@ -900,4 +988,5 @@ bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, float value
return true;
}
#endif // PCSX2_CORE
#endif // PCSX2_CORE

View File

@ -43,12 +43,26 @@ namespace ImGuiManager
/// Returns the scale of all on-screen elements.
float GetGlobalScale();
/// Returns true if fullscreen fonts are present.
bool HasFullscreenFonts();
/// Allocates/adds fullscreen fonts if they're not loaded.
bool AddFullscreenFontsIfMissing();
/// Returns the standard font for external drawing.
ImFont* GetStandardFont();
/// Returns the fixed-width font for external drawing.
ImFont* GetFixedFont();
/// Returns the medium font for external drawing, scaled by ImGuiFullscreen.
/// This font is allocated on demand.
ImFont* GetMediumFont();
/// Returns the large font for external drawing, scaled by ImGuiFullscreen.
/// This font is allocated on demand.
ImFont* GetLargeFont();
#ifdef PCSX2_CORE
/// Returns true if imgui wants to intercept text input.
bool WantsTextInput();

View File

@ -40,6 +40,7 @@
#include "pcsx2/GS.h"
#ifdef PCSX2_CORE
#include "pcsx2/HostSettings.h"
#include "pcsx2/Frontend/FullscreenUI.h"
#include "pcsx2/Frontend/InputManager.h"
#endif
@ -281,7 +282,13 @@ static bool DoGSOpen(GSRendererType renderer, u8* basemem)
return false;
}
#ifdef PCSX2_CORE
// Don't override the fullscreen UI's vsync choice.
if (!FullscreenUI::IsInitialized())
display->SetVSync(EmuConfig.GetEffectiveVsyncMode());
#else
display->SetVSync(EmuConfig.GetEffectiveVsyncMode());
#endif
GSConfig.OsdShowGPU = EmuConfig.GS.OsdShowGPU && display->SetGPUTimingEnabled(true);
g_gs_renderer->SetRegsMem(basemem);

View File

@ -361,7 +361,8 @@ void PAD::SetDefaultConfig(SettingsInterface& si)
// si.SetStringValue("Hotkeys", "FrameAdvance", "Keyboard"); TBD
// si.SetStringValue("Hotkeys", "IncreaseSpeed", "Keyboard"); TBD
// si.SetStringValue("Hotkeys", "ResetVM", "Keyboard"); TBD
si.SetStringValue("Hotkeys", "ShutdownVM", "Keyboard/Escape");
// si.SetStringValue("Hotkeys", "ShutdownVM", "Keyboard"); TBD
si.SetStringValue("Hotkeys", "OpenPauseMenu", "Keyboard/Escape");
si.SetStringValue("Hotkeys", "ToggleFrameLimit", "Keyboard/F4");
si.SetStringValue("Hotkeys", "TogglePause", "Keyboard/Space");
si.SetStringValue("Hotkeys", "ToggleSlowMotion", "Keyboard/Shift & Keyboard/Backtab");

View File

@ -36,6 +36,12 @@
#define VER_INTERNAL_NAME_STR VER_ORIGINAL_FILENAME_STR
#define VER_COPYRIGHT_STR "Copyright (C) 2022"
#define PCSX2_WEBSITE_URL "https://pcsx2.net/"
#define PCSX2_FORUMS_URL "https://forums.pcsx2.net/"
#define PCSX2_GITHUB_URL "https://github.com/PCSX2/pcsx2"
#define PCSX2_LICENSE_URL "https://github.com/PCSX2/pcsx2/blob/master/pcsx2/Docs/License.txt"
#define PCSX2_DISCORD_URL "https://discord.com/invite/TCz3t9k"
static const bool PCSX2_isReleaseVersion = 0;
class SysCoreThread;

View File

@ -56,6 +56,7 @@
#include "DebugTools/MIPSAnalyst.h"
#include "DebugTools/SymbolMap.h"
#include "Frontend/FullscreenUI.h"
#include "Frontend/INISettingsInterface.h"
#include "Frontend/InputManager.h"
#include "Frontend/GameList.h"
@ -72,7 +73,6 @@
namespace VMManager
{
static void LoadSettings();
static void ApplyGameFixes();
static bool UpdateGameSettingsLayer();
static void CheckForConfigChanges(const Pcsx2Config& old_config);
@ -137,6 +137,7 @@ static s32 s_current_save_slot = 1;
static u32 s_frame_advance_count = 0;
static u32 s_mxcsr_saved;
static std::optional<LimiterModeType> s_limiter_mode_prior_to_hold_interaction;
static bool s_gs_open_on_initialize = false;
bool VMManager::PerformEarlyHardwareChecks(const char** error)
{
@ -200,9 +201,15 @@ void VMManager::SetState(VMState state)
SPU2SetOutputPaused(state == VMState::Paused);
if (state == VMState::Paused)
{
Host::OnVMPaused();
FullscreenUI::OnVMPaused();
}
else
{
Host::OnVMResumed();
FullscreenUI::OnVMResumed();
}
}
}
@ -256,6 +263,12 @@ bool VMManager::Internal::InitializeGlobals()
x86caps.CalculateMHz();
SysLogMachineCaps();
if (GSinit() != 0)
{
Host::ReportErrorAsync("Error", "Failed to initialize GS (GSinit()).");
return false;
}
return true;
}
@ -683,6 +696,12 @@ void VMManager::UpdateRunningGame(bool resetting, bool game_starting)
GetMTGS().SendGameCRC(new_crc);
Host::OnGameChanged(s_disc_path, s_game_serial, s_game_name, s_game_crc);
if (FullscreenUI::IsInitialized())
{
GetMTGS().RunOnGSThread([disc_path = s_disc_path, game_serial = s_game_serial, game_name = s_game_name, game_crc = s_game_crc]() {
FullscreenUI::OnRunningGameChanged(std::move(disc_path), std::move(game_serial), std::move(game_name), game_crc);
});
}
#if 0
// TODO: Enable this when the debugger is added to Qt, and it's active. Otherwise, this is just a waste of time.
@ -846,8 +865,6 @@ bool VMManager::Initialize(VMBootParameters boot_params)
Host::OnVMDestroyed();
};
LoadSettings();
std::string state_to_load;
if (!ApplyBootParameters(std::move(boot_params), &state_to_load))
return false;
@ -870,14 +887,18 @@ bool VMManager::Initialize(VMBootParameters boot_params)
ScopedGuard close_cdvd = [] { DoCDVDclose(); };
Console.WriteLn("Opening GS...");
if (!GetMTGS().WaitForOpen())
s_gs_open_on_initialize = GetMTGS().IsOpen();
if (!s_gs_open_on_initialize && !GetMTGS().WaitForOpen())
{
// we assume GS is going to report its own error
Console.WriteLn("Failed to open GS.");
return false;
}
ScopedGuard close_gs = []() { GetMTGS().WaitForClose(); };
ScopedGuard close_gs = []() {
if (!s_gs_open_on_initialize)
GetMTGS().WaitForClose();
};
Console.WriteLn("Opening SPU2...");
if (SPU2init() != 0 || SPU2open() != 0)
@ -964,6 +985,7 @@ bool VMManager::Initialize(VMBootParameters boot_params)
Console.WriteLn("VM subsystems initialized in %.2f ms", init_timer.GetTimeMilliseconds());
s_state.store(VMState::Paused, std::memory_order_release);
Host::OnVMStarted();
FullscreenUI::OnVMStarted();
UpdateRunningGame(true, false);
@ -1047,7 +1069,14 @@ void VMManager::Shutdown(bool save_resume_state)
DoCDVDclose();
FWclose();
FileMcd_EmuClose();
GetMTGS().WaitForClose();
// If the fullscreen UI is running, do a hardware reset on the GS
// so that the texture cache and targets are all cleared.
if (s_gs_open_on_initialize)
GetMTGS().ResetGS(true);
else
GetMTGS().WaitForClose();
USBshutdown();
SPU2shutdown();
PADshutdown();
@ -1058,6 +1087,7 @@ void VMManager::Shutdown(bool save_resume_state)
s_state.store(VMState::Shutdown, std::memory_order_release);
Host::OnVMDestroyed();
FullscreenUI::OnVMDestroyed();
}
void VMManager::Reset()
@ -1623,20 +1653,27 @@ void VMManager::CheckForMemoryCardConfigChanges(const Pcsx2Config& old_config)
void VMManager::CheckForConfigChanges(const Pcsx2Config& old_config)
{
CheckForCPUConfigChanges(old_config);
CheckForGSConfigChanges(old_config);
CheckForFramerateConfigChanges(old_config);
CheckForPatchConfigChanges(old_config);
CheckForSPU2ConfigChanges(old_config);
CheckForDEV9ConfigChanges(old_config);
CheckForMemoryCardConfigChanges(old_config);
if (EmuConfig.EnableCheats != old_config.EnableCheats ||
EmuConfig.EnableWideScreenPatches != old_config.EnableWideScreenPatches ||
EmuConfig.EnableNoInterlacingPatches != old_config.EnableNoInterlacingPatches)
if (HasValidVM())
{
VMManager::ReloadPatches(true, true);
CheckForCPUConfigChanges(old_config);
CheckForFramerateConfigChanges(old_config);
CheckForPatchConfigChanges(old_config);
CheckForSPU2ConfigChanges(old_config);
CheckForDEV9ConfigChanges(old_config);
CheckForMemoryCardConfigChanges(old_config);
if (EmuConfig.EnableCheats != old_config.EnableCheats ||
EmuConfig.EnableWideScreenPatches != old_config.EnableWideScreenPatches ||
EmuConfig.EnableNoInterlacingPatches != old_config.EnableNoInterlacingPatches)
{
VMManager::ReloadPatches(true, true);
}
}
// For the big picture UI, we still need to update GS settings, since it's running,
// and we don't update its config when we start the VM.
if (HasValidVM() || GetMTGS().IsOpen())
CheckForGSConfigChanges(old_config);
}
void VMManager::ApplySettings()
@ -1654,9 +1691,7 @@ void VMManager::ApplySettings()
const Pcsx2Config old_config(EmuConfig);
LoadSettings();
if (HasValidVM())
CheckForConfigChanges(old_config);
CheckForConfigChanges(old_config);
}
bool VMManager::ReloadGameSettings()
@ -1741,6 +1776,10 @@ static void HotkeySaveStateSlot(s32 slot)
}
BEGIN_HOTKEY_LIST(g_vm_manager_hotkeys)
DEFINE_HOTKEY("OpenPauseMenu", "System", "Open Pause Menu", [](s32 pressed) {
if (!pressed)
FullscreenUI::OpenPauseMenu();
})
DEFINE_HOTKEY("TogglePause", "System", "Toggle Pause", [](s32 pressed) {
if (!pressed && VMManager::HasValidVM())
VMManager::SetPaused(VMManager::GetState() != VMState::Paused);
@ -1801,7 +1840,7 @@ DEFINE_HOTKEY("FrameAdvance", "System", "Frame Advance", [](s32 pressed) {
})
DEFINE_HOTKEY("ShutdownVM", "System", "Shut Down Virtual Machine", [](s32 pressed) {
if (!pressed && VMManager::HasValidVM())
Host::RequestVMShutdown(true, true);
Host::RequestVMShutdown(true, true, EmuConfig.SaveStateOnShutdown);
})
DEFINE_HOTKEY("ResetVM", "System", "Reset Virtual Machine", [](s32 pressed) {
if (!pressed && VMManager::HasValidVM())
@ -1858,7 +1897,6 @@ DEFINE_HOTKEY_SAVESTATE_X(10)
DEFINE_HOTKEY_LOADSTATE_X(10)
#undef DEFINE_HOTKEY_SAVESTATE_X
#undef DEFINE_HOTKEY_LOADSTATE_X
END_HOTKEY_LIST()
#ifdef _WIN32

View File

@ -76,6 +76,9 @@ namespace VMManager
/// Returns the name of the disc/executable currently running.
std::string GetGameName();
/// Loads global settings (i.e. EmuConfig).
void LoadSettings();
/// Initializes all system components.
bool Initialize(VMBootParameters boot_params);
@ -249,7 +252,7 @@ namespace Host
void RequestExit(bool save_state_if_running);
/// Requests shut down of the current virtual machine.
void RequestVMShutdown(bool allow_confirm, bool allow_save_state);
void RequestVMShutdown(bool allow_confirm, bool allow_save_state, bool default_save_state);
/// Returns true if the hosting application is currently fullscreen.
bool IsFullscreen();

View File

@ -188,7 +188,9 @@
<ClCompile Include="DEV9\Win32\tap-win32.cpp" />
<ClCompile Include="Frontend\D3D11HostDisplay.cpp" />
<ClCompile Include="Frontend\D3D12HostDisplay.cpp" />
<ClCompile Include="Frontend\FullscreenUI.cpp" />
<ClCompile Include="Frontend\GameList.cpp" />
<ClCompile Include="Frontend\ImGuiFullscreen.cpp" />
<ClCompile Include="Frontend\ImGuiManager.cpp" />
<ClCompile Include="Frontend\imgui_impl_dx11.cpp" />
<ClCompile Include="Frontend\imgui_impl_dx12.cpp" />
@ -508,7 +510,9 @@
<ClInclude Include="DEV9\Win32\tap.h" />
<ClInclude Include="Frontend\D3D11HostDisplay.h" />
<ClInclude Include="Frontend\D3D12HostDisplay.h" />
<ClInclude Include="Frontend\FullscreenUI.h" />
<ClInclude Include="Frontend\GameList.h" />
<ClInclude Include="Frontend\ImGuiFullscreen.h" />
<ClInclude Include="Frontend\ImGuiManager.h" />
<ClInclude Include="Frontend\imgui_impl_dx11.h" />
<ClInclude Include="Frontend\imgui_impl_dx12.h" />

View File

@ -1275,6 +1275,12 @@
<ClCompile Include="Frontend\LogSink.cpp">
<Filter>Host</Filter>
</ClCompile>
<ClCompile Include="Frontend\ImGuiFullscreen.cpp">
<Filter>Host</Filter>
</ClCompile>
<ClCompile Include="Frontend\FullscreenUI.cpp">
<Filter>Host</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Patch.h">
@ -2119,6 +2125,12 @@
<ClInclude Include="Frontend\LogSink.h">
<Filter>Host</Filter>
</ClInclude>
<ClInclude Include="Frontend\ImGuiFullscreen.h">
<Filter>Host</Filter>
</ClInclude>
<ClInclude Include="Frontend\FullscreenUI.h">
<Filter>Host</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CustomBuildStep Include="rdebug\deci2.h">

View File

@ -0,0 +1,95 @@
#!/usr/bin/env python3
import sys
import os
import glob
import re
import functools
# PCSX2 - PS2 Emulator for PCs
# Copyright (C) 2002-2022 PCSX2 Dev Team
#
# PCSX2 is free software: you can redistribute it and/or modify it under the terms
# of the GNU Lesser General Public License as published by the Free Software Found-
# ation, either version 3 of the License, or (at your option) any later version.
#
# PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with PCSX2.
# If not, see <http://www.gnu.org/licenses/>.
# pylint: disable=bare-except, disable=missing-function-docstring
src_dirs = [os.path.join(os.path.dirname(__file__), "..", "pcsx2"), os.path.join(os.path.dirname(__file__), "..", "pcsx2-qt")]
fa_file = os.path.join(os.path.dirname(__file__), "..", "3rdparty", "include", "IconsFontAwesome5.h")
dst_file = os.path.join(os.path.dirname(__file__), "..", "pcsx2", "Frontend", "ImguiManager.cpp")
all_source_files = list(functools.reduce(lambda prev, src_dir: prev + glob.glob(os.path.join(src_dir, "**", "*.cpp"), recursive=True) + \
glob.glob(os.path.join(src_dir, "**", "*.h"), recursive=True) + \
glob.glob(os.path.join(src_dir, "**", "*.inl"), recursive=True), src_dirs, []))
tokens = set()
for filename in all_source_files:
data = None
with open(filename, "r") as f:
try:
data = f.read()
except:
continue
tokens = tokens.union(set(re.findall("(ICON_FA_[a-zA-Z0-9_]+)", data)))
print("{} tokens found.".format(len(tokens)))
if len(tokens) == 0:
sys.exit(0)
u8_encodings = {}
with open(fa_file, "r") as f:
for line in f.readlines():
match = re.match("#define (ICON_FA_[^ ]+) \"([^\"]+)\"", line)
if match is None:
continue
u8_encodings[match[1]] = bytes.fromhex(match[2].replace("\\x", ""))
out_pattern = "(static constexpr ImWchar range_fa\[\] = \{)[0-9A-Z_a-z, \n]+(\};)"
codepoints = list()
for token in tokens:
u8_bytes = u8_encodings[token]
u8 = str(u8_bytes, "utf-8")
u16 = u8.encode("utf-16le")
if len(u16) > 2:
raise ValueError("{} too long".format(u8_bytes))
codepoint = int.from_bytes(u16, byteorder="little", signed=False)
codepoints.append(codepoint)
codepoints.sort()
codepoints.append(0) # null terminator
startc = codepoints[0]
endc = None
pairs = [startc]
for codepoint in codepoints:
if endc is not None and (endc + 1) != codepoint:
pairs.append(endc)
pairs.append(codepoint)
startc = codepoint
endc = codepoint
else:
endc = codepoint
pairs.append(endc)
pairs_str = ",".join(map("0x{:x}".format, pairs))
with open(dst_file, "r") as f:
original = f.read()
updated = re.sub(out_pattern, "\\1 " + pairs_str + " \\2", original)
if original != updated:
with open(dst_file, "w") as f:
f.write(updated)
print("Updated {}".format(dst_file))
else:
print("Skipping updating {}".format(dst_file))