Qt: Make screen widget polymorphic

This commit is contained in:
wheremyfoodat 2025-07-26 17:12:40 +03:00
commit 1e34440479
9 changed files with 128 additions and 78 deletions

View file

@ -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)

View file

@ -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<GL::Context> 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<WindowInfo> getWindowInfo();
private:
qreal devicePixelRatioFromScreen() const;
int scaledWindowWidth() const;
int scaledWindowHeight() const;
std::optional<WindowInfo> getWindowInfo();
void reloadScreenCoordinates();
};

View file

@ -0,0 +1,18 @@
#pragma once
#include <memory>
#include "gl/context.h"
#include "panda_qt/screen/screen.hpp"
class ScreenWidgetGL : public ScreenWidget {
std::unique_ptr<GL::Context> 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;
};

View file

@ -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;
};

View file

@ -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;

View file

@ -5,9 +5,9 @@
#import <QWindow>
#import <QuartzCore/QuartzCore.hpp>
#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];

View file

@ -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 <QGuiApplication>
#include <QScreen>
#include <QWindow>
#include <algorithm>
#include <array>
#include <cmath>
#include <optional>
@ -12,9 +14,11 @@
#include <qpa/qplatformnativeinterface.h>
#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<GL::Context::Version, 8> 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> 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) {

View file

@ -0,0 +1,53 @@
#include "panda_qt/screen/screen_gl.hpp"
#include <array>
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<GL::Context::Version, 8> 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> 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(); }

View file

@ -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; }