Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Qt GL screen widget #301

Merged
merged 5 commits into from
Sep 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/Qt_Build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ jobs:
sudo apt-get update && sudo apt install libx11-dev libgl1-mesa-glx mesa-common-dev libfuse2 libwayland-dev
sudo add-apt-repository -y ppa:savoury1/qt-6-2
sudo apt update
sudo apt install qt6-base-dev
sudo apt install qt6-base-dev qt6-base-private-dev

- name: Install newer Clang
run: |
Expand Down
18 changes: 15 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -179,9 +179,15 @@ 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})
include_directories(${Qt6Gui_PRIVATE_INCLUDE_DIRS})
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
Expand Down Expand Up @@ -353,7 +359,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
Expand Down Expand Up @@ -400,6 +407,11 @@ if(ENABLE_QT_GUI)
if(LINUX OR FREEBSD)
find_package(X11 REQUIRED)
target_link_libraries(Alber PRIVATE ${X11_LIBRARIES})

if(ENABLE_OPENGL)
find_package(OpenGL REQUIRED COMPONENTS OpenGL EGL GLX)
target_link_libraries(Alber PRIVATE OpenGL::OpenGL OpenGL::EGL OpenGL::GLX)
endif()
endif()
else()
target_compile_definitions(Alber PUBLIC "PANDA3DS_FRONTEND_SDL=1")
Expand Down
23 changes: 23 additions & 0 deletions include/panda_qt/screen.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once
#include <QWidget>
#include <memory>

#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);

private:
std::unique_ptr<GL::Context> glContext = nullptr;
bool createGLContext();

qreal devicePixelRatioFromScreen() const;
int scaledWindowWidth() const;
int scaledWindowHeight() const;
std::optional<WindowInfo> getWindowInfo();
};
20 changes: 13 additions & 7 deletions src/panda_qt/main.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
#include <QApplication>
#include <QtWidgets>

#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();
screen.resize(320, 240);

return app.exec();
}
120 changes: 120 additions & 0 deletions src/panda_qt/screen.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#include "opengl.hpp"
// 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>

#if !defined(_WIN32) && !defined(APPLE)
#include <qpa/qplatformnativeinterface.h>
#endif

#include "panda_qt/screen.hpp"

// OpenGL 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
ScreenWidget::ScreenWidget(QWidget* parent) : 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");
}

// Make our context current to use it
glContext->MakeCurrent();
// Enable VSync for now
glContext->SetSwapInterval(1);

OpenGL::setViewport(320, 240);
OpenGL::setClearColor(1.0, 0.0, 0.0, 1.0);
OpenGL::clearColor();

// Swap buffers to display our red as a test
glContext->SwapBuffers();
}

bool ScreenWidget::createGLContext() {
// List of GL context versions we will try. Anything 4.1+ is good
static constexpr std::array<GL::Context::Version, 6> 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> 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<qreal>(1);
}

int ScreenWidget::scaledWindowWidth() const {
return std::max(static_cast<int>(std::ceil(static_cast<qreal>(width()) * devicePixelRatioFromScreen())), 1);
}

int ScreenWidget::scaledWindowHeight() const {
return std::max(static_cast<int>(std::ceil(static_cast<qreal>(height()) * devicePixelRatioFromScreen())), 1);
}

std::optional<WindowInfo> 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<void*>(winId());
#elif defined(__APPLE__)
wi.type = WindowInfo::Type::MacOS;
wi.window_handle = reinterpret_cast<void*>(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<void*>(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<u32>(scaledWindowWidth());
wi.surface_height = static_cast<u32>(scaledWindowHeight());
wi.surface_scale = static_cast<float>(devicePixelRatioFromScreen());

return wi;
}
#endif