diff --git a/CMakeLists.txt b/CMakeLists.txt index a7e39949..8e8e9428 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -734,7 +734,7 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE) src/panda_qt/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp src/panda_qt/cheats_window.cpp src/panda_qt/mappings.cpp src/panda_qt/patch_window.cpp src/panda_qt/elided_label.cpp src/panda_qt/shader_editor.cpp src/panda_qt/translations.cpp src/panda_qt/thread_debugger.cpp src/panda_qt/cpu_debugger.cpp src/panda_qt/dsp_debugger.cpp src/panda_qt/input_window.cpp - src/panda_qt/screen/screen.cpp + src/panda_qt/screen/screen.cpp src/panda_qt/screen/screen_gl.cpp src/panda_qt/screen/screen_mtl.cpp ) set(FRONTEND_HEADER_FILES include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp @@ -742,6 +742,7 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE) include/panda_qt/patch_window.hpp include/panda_qt/elided_label.hpp include/panda_qt/shader_editor.hpp include/panda_qt/thread_debugger.hpp include/panda_qt/cpu_debugger.hpp include/panda_qt/dsp_debugger.hpp include/panda_qt/disabled_widget_overlay.hpp include/panda_qt/input_window.hpp include/panda_qt/screen/screen.hpp + include/panda_qt/screen/screen_gl.hpp include/panda_qt/screen/screen_mtl.hpp ) if (APPLE AND ENABLE_METAL) diff --git a/include/panda_qt/screen/screen.hpp b/include/panda_qt/screen/screen.hpp index 2a3d999d..2731da3c 100644 --- a/include/panda_qt/screen/screen.hpp +++ b/include/panda_qt/screen/screen.hpp @@ -7,7 +7,7 @@ #include "screen_layout.hpp" #include "window_info.h" -// OpenGL widget for drawing the 3DS screen +// Abstract screen widget for drawing the 3DS screen. We've got a child class for each graphics API (ScreenWidgetGL, ScreenWidgetMTL, ...) class ScreenWidget : public QWidget { Q_OBJECT @@ -18,11 +18,9 @@ class ScreenWidget : public QWidget { ScreenWidget(API api, ResizeCallback resizeCallback, QWidget* parent = nullptr); void resizeEvent(QResizeEvent* event) override; - // Called by the emulator thread for resizing the actual GL surface, since the emulator thread owns the GL context - void resizeSurface(u32 width, u32 height); - GL::Context* getGLContext() { return glContext.get(); } - void* getMTKLayer() { return mtkLayer; } + virtual GL::Context* getGLContext() { return nullptr; } + virtual void* getMTKLayer() { return nullptr; } // Dimensions of our output surface u32 surfaceWidth = 0; @@ -35,8 +33,7 @@ class ScreenWidget : public QWidget { API api = API::OpenGL; - // Coordinates (x/y/width/height) for the two screens in window space, used for properly handling touchscreen regardless - // of layout or resizing + // Coordinates (x/y/width/height) for the two screens in window space, used for properly handling touchscreen ScreenLayout::WindowCoordinates screenCoordinates; // Screen layouts and sizes ScreenLayout::Layout screenLayout = ScreenLayout::Layout::Default; @@ -44,24 +41,20 @@ class ScreenWidget : public QWidget { void reloadScreenLayout(ScreenLayout::Layout newLayout, float newTopScreenSize); - private: - // GL context for GL-based renderers - std::unique_ptr glContext = nullptr; - - // CA::MetalLayer for the Metal renderer - void* mtkLayer = nullptr; + // Called by the emulator thread on OpenGL for resizing the actual GL surface, since the emulator thread owns the GL context + virtual void resizeSurface(u32 width, u32 height) {}; + protected: ResizeCallback resizeCallback; - bool createGLContext(); - bool createMetalContext(); - - void resizeMetalView(); + virtual bool createContext() = 0; + virtual void resizeDisplay() = 0; + std::optional getWindowInfo(); + private: qreal devicePixelRatioFromScreen() const; int scaledWindowWidth() const; int scaledWindowHeight() const; - std::optional getWindowInfo(); void reloadScreenCoordinates(); }; diff --git a/include/panda_qt/screen/screen_gl.hpp b/include/panda_qt/screen/screen_gl.hpp new file mode 100644 index 00000000..04a2c11b --- /dev/null +++ b/include/panda_qt/screen/screen_gl.hpp @@ -0,0 +1,18 @@ +#pragma once +#include + +#include "gl/context.h" +#include "panda_qt/screen/screen.hpp" + +class ScreenWidgetGL : public ScreenWidget { + std::unique_ptr glContext = nullptr; + + public: + ScreenWidgetGL(API api, ResizeCallback resizeCallback, QWidget* parent = nullptr); + + virtual GL::Context* getGLContext() override; + virtual bool createContext() override; + + virtual void resizeDisplay() override; + virtual void resizeSurface(u32 width, u32 height) override; +}; \ No newline at end of file diff --git a/include/panda_qt/screen/screen_mtl.hpp b/include/panda_qt/screen/screen_mtl.hpp new file mode 100644 index 00000000..db18436b --- /dev/null +++ b/include/panda_qt/screen/screen_mtl.hpp @@ -0,0 +1,17 @@ +#pragma once +#include "panda_qt/screen/screen.hpp" + +class ScreenWidgetMTL : public ScreenWidget { + void* mtkLayer = nullptr; + + // Objective-C++ functions for handling the Metal context + bool createMetalContext(); + void resizeMetalView(); + + public: + ScreenWidgetMTL(API api, ResizeCallback resizeCallback, QWidget* parent = nullptr); + + virtual void* getMTKLayer() override; + virtual bool createContext() override; + virtual void resizeDisplay() override; +}; \ No newline at end of file diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index e3ca79a1..7d64f350 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -10,6 +10,7 @@ #include "cheats.hpp" #include "input_mappings.hpp" #include "panda_qt/dsp_debugger.hpp" +#include "panda_qt/screen/screen_gl.hpp" #include "sdl_sensors.hpp" #include "services/dsp.hpp" #include "version.hpp" @@ -37,7 +38,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) api = ScreenWidget::API::Metal; // We pass a callback to the screen widget that will be triggered every time we resize the screen - screen = new ScreenWidget(api, [this](u32 width, u32 height) { handleScreenResize(width, height); }, this); + screen = new ScreenWidgetGL(api, [this](u32 width, u32 height) { handleScreenResize(width, height); }, this); setCentralWidget(screen); appRunning = true; diff --git a/src/panda_qt/screen/metal_context.mm b/src/panda_qt/screen/metal_context.mm index d5bf6d85..bf8f0ae3 100644 --- a/src/panda_qt/screen/metal_context.mm +++ b/src/panda_qt/screen/metal_context.mm @@ -5,9 +5,9 @@ #import #import -#import "panda_qt/screen/screen.hpp" +#import "panda_qt/screen/screen_mtl.hpp" -bool ScreenWidget::createMetalContext() { +bool ScreenWidgetMTL::createMetalContext() { NSView* nativeView = (NSView*)this->winId(); CAMetalLayer* metalLayer = [CAMetalLayer layer]; @@ -41,7 +41,7 @@ bool ScreenWidget::createMetalContext() { return true; } -void ScreenWidget::resizeMetalView() { +void ScreenWidgetMTL::resizeMetalView() { NSView* view = (NSView*)this->windowHandle()->winId(); CAMetalLayer* metalLayer = (CAMetalLayer*)[view layer]; diff --git a/src/panda_qt/screen/screen.cpp b/src/panda_qt/screen/screen.cpp index 45b17317..534f06d7 100644 --- a/src/panda_qt/screen/screen.cpp +++ b/src/panda_qt/screen/screen.cpp @@ -1,10 +1,12 @@ +#ifdef PANDA3DS_ENABLE_OPENGL #include "opengl.hpp" +#endif // opengl.hpp must be included at the very top. This comment exists to make clang-format not reorder it :p + #include #include #include #include -#include #include #include @@ -12,9 +14,11 @@ #include #endif +#include "panda_qt/screen//screen_mtl.hpp" #include "panda_qt/screen/screen.hpp" +#include "panda_qt/screen/screen_gl.hpp" -// OpenGL screen widget, based on https://github.com/stenzek/duckstation/blob/master/src/duckstation-qt/displaywidget.cpp +// Screen widget, based on https://github.com/stenzek/duckstation/blob/master/src/duckstation-qt/displaywidget.cpp // and https://github.com/melonDS-emu/melonDS/blob/master/src/frontend/qt_sdl/main.cpp #ifdef PANDA3DS_ENABLE_OPENGL @@ -28,20 +32,7 @@ ScreenWidget::ScreenWidget(API api, ResizeCallback resizeCallback, QWidget* pare setFocusPolicy(Qt::StrongFocus); setMouseTracking(true); - if (api == API::OpenGL) { - if (!createGLContext()) { - Helpers::panic("Failed to create GL context for display"); - } - } else if (api == API::Metal) { - if (!createMetalContext()) { - Helpers::panic("Failed to create Metal context for display"); - } - } else { - Helpers::panic("Unspported api for Qt screen widget"); - } - - resize(800, 240 * 4); - show(); + // The graphics context, as well as resizing and showing the widget, is handled by the screen backend } void ScreenWidget::resizeEvent(QResizeEvent* event) { @@ -55,22 +46,8 @@ void ScreenWidget::resizeEvent(QResizeEvent* event) { this->windowInfo = *windowInfo; } - if (api == API::Metal) { - resizeMetalView(); - } - reloadScreenCoordinates(); - // This will call take care of calling resizeSurface from the emulator thread, as the GL renderer must resize from the emu thread - resizeCallback(surfaceWidth, surfaceHeight); -} - -// Note: This will run on the emulator thread, we don't want any Qt calls happening there. -void ScreenWidget::resizeSurface(u32 width, u32 height) { - if (api == API::OpenGL && (previousWidth != width || previousHeight != height)) { - if (glContext) { - glContext->ResizeSurface(width, height); - } - } + resizeDisplay(); } void ScreenWidget::reloadScreenCoordinates() { @@ -84,30 +61,6 @@ void ScreenWidget::reloadScreenLayout(ScreenLayout::Layout newLayout, float newT reloadScreenCoordinates(); } -bool ScreenWidget::createGLContext() { - // List of GL context versions we will try. Anything 4.1+ is good for desktop OpenGL, and 3.1+ for OpenGL ES - 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}, - GL::Context::Version{GL::Context::Profile::ES, 3, 2}, GL::Context::Version{GL::Context::Profile::ES, 3, 1}, - }; - - std::optional windowInfo = getWindowInfo(); - if (windowInfo.has_value()) { - this->windowInfo = *windowInfo; - - glContext = GL::Context::Create(*getWindowInfo(), versionsToTry); - if (glContext == nullptr) { - return false; - } - - glContext->DoneCurrent(); - } - - return glContext != nullptr; -} - qreal ScreenWidget::devicePixelRatioFromScreen() const { const QScreen* screenForRatio = windowHandle()->screen(); if (!screenForRatio) { diff --git a/src/panda_qt/screen/screen_gl.cpp b/src/panda_qt/screen/screen_gl.cpp new file mode 100644 index 00000000..da5d230c --- /dev/null +++ b/src/panda_qt/screen/screen_gl.cpp @@ -0,0 +1,53 @@ +#include "panda_qt/screen/screen_gl.hpp" + +#include + +ScreenWidgetGL::ScreenWidgetGL(API api, ResizeCallback resizeCallback, QWidget* parent) : ScreenWidget(api, resizeCallback, parent) { + // On Wayland + OpenGL, we have to show the window before we can create a graphics context. + resize(800, 240 * 4); + show(); + + if (!createContext()) { + Helpers::panic("Failed to create GL context for display"); + } +} + +bool ScreenWidgetGL::createContext() { + // List of GL context versions we will try. Anything 4.1+ is good for desktop OpenGL, and 3.1+ for OpenGL ES + 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}, + GL::Context::Version{GL::Context::Profile::ES, 3, 2}, GL::Context::Version{GL::Context::Profile::ES, 3, 1}, + }; + + std::optional windowInfo = getWindowInfo(); + if (windowInfo.has_value()) { + this->windowInfo = *windowInfo; + + glContext = GL::Context::Create(*getWindowInfo(), versionsToTry); + if (glContext == nullptr) { + return false; + } + + glContext->DoneCurrent(); + } + + return glContext != nullptr; +} + +void ScreenWidgetGL::resizeDisplay() { + // This will call take care of calling resizeSurface from the emulator thread, as the GL renderer must resize from the emu thread + resizeCallback(surfaceWidth, surfaceHeight); +} + +// Note: This will run on the emulator thread, we don't want any Qt calls happening there. +void ScreenWidgetGL::resizeSurface(u32 width, u32 height) { + if (previousWidth != width || previousHeight != height) { + if (glContext) { + glContext->ResizeSurface(width, height); + } + } +} + +GL::Context* ScreenWidgetGL::getGLContext() { return glContext.get(); } \ No newline at end of file diff --git a/src/panda_qt/screen/screen_mtl.cpp b/src/panda_qt/screen/screen_mtl.cpp new file mode 100644 index 00000000..588363fa --- /dev/null +++ b/src/panda_qt/screen/screen_mtl.cpp @@ -0,0 +1,14 @@ +#include "panda_qt/screen/screen_mtl.hpp" + +ScreenWidgetMTL::ScreenWidgetMTL(API api, ResizeCallback resizeCallback, QWidget* parent) : ScreenWidget(api, resizeCallback, parent) { + if (!createContext()) { + Helpers::panic("Failed to create Metal context for display"); + } + + resize(800, 240 * 4); + show(); +} + +void ScreenWidgetMTL::resizeDisplay() { resizeMetalView(); } +bool ScreenWidgetMTL::createContext() { return createMetalContext(); } +void* ScreenWidgetMTL::getMTKLayer() { return mtkLayer; } \ No newline at end of file