diff --git a/CMakeLists.txt b/CMakeLists.txt index b84dc470..254f2d2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,7 +63,7 @@ if(ENABLE_DISCORD_RPC AND NOT ANDROID) include_directories(third_party/discord-rpc/include) endif() -if (ENABLE_QT_GUI) +if(ENABLE_QT_GUI) find_package(Qt6 REQUIRED COMPONENTS Widgets) # We can't use qt_standard_project_setup since it's Qt 6.3+ and we don't need to set the minimum that high @@ -179,9 +179,14 @@ set(RENDERER_SW_SOURCE_FILES src/core/renderer_sw/renderer_sw.cpp) # Frontend source files if(ENABLE_QT_GUI) - set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp) + set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp) + set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp) + + source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES}) + source_group("Header Files\\Qt" FILES ${FRONTEND_HEADER_FILES}) else() set(FRONTEND_SOURCE_FILES src/panda_sdl/main.cpp) + set(FRONTEND_HEADER_FILES "") endif() set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp @@ -353,7 +358,8 @@ endif() source_group("Header Files\\Core" FILES ${HEADER_FILES}) set(ALL_SOURCES ${SOURCE_FILES} ${FRONTEND_SOURCE_FILES} ${FS_SOURCE_FILES} ${CRYPTO_SOURCE_FILES} ${KERNEL_SOURCE_FILES} - ${LOADER_SOURCE_FILES} ${SERVICE_SOURCE_FILES} ${APPLET_SOURCE_FILES} ${RENDERER_SW_SOURCE_FILES} ${PICA_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HEADER_FILES}) + ${LOADER_SOURCE_FILES} ${SERVICE_SOURCE_FILES} ${APPLET_SOURCE_FILES} ${RENDERER_SW_SOURCE_FILES} ${PICA_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} + ${HEADER_FILES} ${FRONTEND_HEADER_FILES}) if(ENABLE_OPENGL) # Add the OpenGL source files to ALL_SOURCES diff --git a/include/panda_qt/screen.hpp b/include/panda_qt/screen.hpp new file mode 100644 index 00000000..bfee19a7 --- /dev/null +++ b/include/panda_qt/screen.hpp @@ -0,0 +1,36 @@ +#pragma once +#include +#include + +#include "gl/context.h" +#include "window_info.h" + +// OpenGL widget for drawing the 3DS screen +class ScreenWidget : public QWidget { + Q_OBJECT + + public: + ScreenWidget(QWidget* parent = nullptr) : QWidget(parent) { + // Create a native window for use with our graphics API of choice + setAutoFillBackground(false); + setAttribute(Qt::WA_NativeWindow, true); + setAttribute(Qt::WA_NoSystemBackground, true); + setAttribute(Qt::WA_PaintOnScreen, true); + setAttribute(Qt::WA_KeyCompression, false); + setFocusPolicy(Qt::StrongFocus); + setMouseTracking(true); + + if (!createGLContext()) { + Helpers::panic("Failed to create GL context for display"); + } + } + + private: + std::unique_ptr glContext = nullptr; + bool createGLContext(); + + qreal devicePixelRatioFromScreen() const; + int scaledWindowWidth() const; + int scaledWindowHeight() const; + std::optional getWindowInfo(); +}; diff --git a/src/panda_qt/main.cpp b/src/panda_qt/main.cpp index e8757f1a..723f7ef6 100644 --- a/src/panda_qt/main.cpp +++ b/src/panda_qt/main.cpp @@ -1,12 +1,17 @@ #include #include +#include "panda_qt/screen.hpp" + int main(int argc, char *argv[]) { - QApplication app(argc, argv); - QWidget window; - - window.resize(320, 240); - window.show(); - window.setWindowTitle("Alber"); - return app.exec(); + QApplication app(argc, argv); + QWidget window; + + window.resize(320, 240); + window.show(); + window.setWindowTitle("Alber"); + ScreenWidget screen(&window); + screen.show(); + + return app.exec(); } \ No newline at end of file diff --git a/src/panda_qt/screen.cpp b/src/panda_qt/screen.cpp new file mode 100644 index 00000000..84bfa48d --- /dev/null +++ b/src/panda_qt/screen.cpp @@ -0,0 +1,86 @@ +#include "panda_qt/screen.hpp" + +#include +#include +#include +#include +#include +#include +#include + +// OpenGL screen widget, based on https://github.com/melonDS-emu/melonDS/blob/master/src/frontend/qt_sdl/main.cpp + +#ifdef PANDA3DS_ENABLE_OPENGL +bool ScreenWidget::createGLContext() { + // List of GL context versions we will try. Anything 4.1+ is good + static constexpr std::array versionsToTry = { + GL::Context::Version{GL::Context::Profile::Core, 4, 6}, GL::Context::Version{GL::Context::Profile::Core, 4, 5}, + GL::Context::Version{GL::Context::Profile::Core, 4, 4}, GL::Context::Version{GL::Context::Profile::Core, 4, 3}, + GL::Context::Version{GL::Context::Profile::Core, 4, 2}, GL::Context::Version{GL::Context::Profile::Core, 4, 1}, + }; + + std::optional windowInfo = getWindowInfo(); + if (windowInfo.has_value()) { + glContext = GL::Context::Create(*getWindowInfo(), versionsToTry); + glContext->DoneCurrent(); + } + + return glContext != nullptr; +} + +qreal ScreenWidget::devicePixelRatioFromScreen() const { + const QScreen* screenForRatio = window()->windowHandle()->screen(); + if (!screenForRatio) { + screenForRatio = QGuiApplication::primaryScreen(); + } + + return screenForRatio ? screenForRatio->devicePixelRatio() : static_cast(1); +} + +int ScreenWidget::scaledWindowWidth() const { + return std::max(static_cast(std::ceil(static_cast(width()) * devicePixelRatioFromScreen())), 1); +} + +int ScreenWidget::scaledWindowHeight() const { + return std::max(static_cast(std::ceil(static_cast(height()) * devicePixelRatioFromScreen())), 1); +} + +std::optional ScreenWidget::getWindowInfo() { + WindowInfo wi; + +// Windows and Apple are easy here since there's no display connection. +#if defined(_WIN32) + wi.type = WindowInfo::Type::Win32; + wi.window_handle = reinterpret_cast(winId()); +#elif defined(__APPLE__) + wi.type = WindowInfo::Type::MacOS; + wi.window_handle = reinterpret_cast(winId()); +#else + QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface(); + const QString platform_name = QGuiApplication::platformName(); + if (platform_name == QStringLiteral("xcb")) { + wi.type = WindowInfo::Type::X11; + wi.display_connection = pni->nativeResourceForWindow("display", windowHandle()); + wi.window_handle = reinterpret_cast(winId()); + } else if (platform_name == QStringLiteral("wayland")) { + wi.type = WindowInfo::Type::Wayland; + QWindow* handle = windowHandle(); + if (handle == nullptr) { + return std::nullopt; + } + + wi.display_connection = pni->nativeResourceForWindow("display", handle); + wi.window_handle = pni->nativeResourceForWindow("surface", handle); + } else { + qCritical() << "Unknown PNI platform " << platform_name; + return std::nullopt; + } +#endif + + wi.surface_width = static_cast(scaledWindowWidth()); + wi.surface_height = static_cast(scaledWindowHeight()); + wi.surface_scale = static_cast(devicePixelRatioFromScreen()); + + return wi; +} +#endif \ No newline at end of file