Port QML frontend to Qt6, use rendering to framebuffer object
authorunC0Rr
Fri, 19 Apr 2024 17:44:55 +0200
changeset 16039 a73b9770467a
parent 16035 1f9f971adec4
child 16040 52b51d92e88d
Port QML frontend to Qt6, use rendering to framebuffer object
qmlfrontend/CMakeLists.txt
qmlfrontend/Page1Form.ui.qml
qmlfrontend/game_view.cpp
qmlfrontend/game_view.h
qmlfrontend/hwengine.h
qmlfrontend/main.cpp
qmlfrontend/players_model.cpp
--- a/qmlfrontend/CMakeLists.txt	Fri Apr 05 13:10:55 2024 +0100
+++ b/qmlfrontend/CMakeLists.txt	Fri Apr 19 17:44:55 2024 +0200
@@ -9,7 +9,7 @@
 set(CMAKE_AUTOMOC ON)
 set(CMAKE_AUTORCC ON)
 
-find_package(Qt5 COMPONENTS Core Quick REQUIRED)
+find_package(Qt6 COMPONENTS Core Quick REQUIRED)
 
 add_executable(${PROJECT_NAME} "main.cpp" "qml.qrc"
     "hwengine.cpp" "hwengine.h"
@@ -25,4 +25,4 @@
     "rooms_model.cpp" "rooms_model.h"
     )
 
-target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Network Qt5::Quick)
+target_link_libraries(${PROJECT_NAME} Qt6::Core Qt6::Network Qt6::Quick)
--- a/qmlfrontend/Page1Form.ui.qml	Fri Apr 05 13:10:55 2024 +0100
+++ b/qmlfrontend/Page1Form.ui.qml	Fri Apr 19 17:44:55 2024 +0200
@@ -20,6 +20,7 @@
 
     RowLayout {
       Layout.alignment: Qt.AlignHCenter
+      Layout.fillHeight: false
 
       Button {
         id: button1
@@ -80,6 +81,10 @@
 
       Layout.fillWidth: true
       Layout.fillHeight: true
+
+      MouseArea {
+        anchors.fill: parent
+      }
     }
   }
 
--- a/qmlfrontend/game_view.cpp	Fri Apr 05 13:10:55 2024 +0100
+++ b/qmlfrontend/game_view.cpp	Fri Apr 19 17:44:55 2024 +0200
@@ -1,15 +1,67 @@
 #include "game_view.h"
 
 #include <QtQuick/qquickwindow.h>
+
 #include <QCursor>
+#include <QOpenGLFramebufferObjectFormat>
+#include <QQuickOpenGLUtils>
 #include <QTimer>
 #include <QtGui/QOpenGLContext>
-#include <QtGui/QOpenGLShaderProgram>
+
+class GameViewRenderer : public QQuickFramebufferObject::Renderer {
+ public:
+  explicit GameViewRenderer() = default;
+
+  GameViewRenderer(const GameViewRenderer&) = delete;
+  GameViewRenderer(GameViewRenderer&&) = delete;
+  GameViewRenderer& operator=(const GameViewRenderer&) = delete;
+  GameViewRenderer& operator=(GameViewRenderer&&) = delete;
+
+  void render() override;
+  QOpenGLFramebufferObject* createFramebufferObject(const QSize& size) override;
+  void synchronize(QQuickFramebufferObject* fbo) override;
+
+  QPointer<GameView> m_gameView;
+  QPointer<QQuickWindow> m_window;
+  bool m_inited{false};
+};
+
+void GameViewRenderer::render() {
+  const auto engine = m_gameView->engineInstance();
+
+  if (!engine) {
+    return;
+  }
+
+  if (!m_inited) {
+    m_inited = true;
+    engine->setOpenGLContext(QOpenGLContext::currentContext());
+  }
+
+  engine->renderFrame();
+
+  QQuickOpenGLUtils::resetOpenGLState();
+}
+
+QOpenGLFramebufferObject* GameViewRenderer::createFramebufferObject(
+    const QSize& size) {
+  QOpenGLFramebufferObjectFormat format;
+  format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
+  format.setSamples(8);
+  auto fbo = new QOpenGLFramebufferObject(size, format);
+  return fbo;
+}
+
+void GameViewRenderer::synchronize(QQuickFramebufferObject* fbo) {
+  if (!m_gameView) {
+    m_gameView = qobject_cast<GameView*>(fbo);
+    m_window = fbo->window();
+  }
+}
 
 GameView::GameView(QQuickItem* parent)
-    : QQuickItem(parent), m_delta(0), m_windowChanged(true) {
-  connect(this, &QQuickItem::windowChanged, this,
-          &GameView::handleWindowChanged);
+    : QQuickFramebufferObject(parent), m_delta(0) {
+  setMirrorVertically(true);
 }
 
 void GameView::tick(quint32 delta) {
@@ -17,98 +69,23 @@
 
   if (window()) {
     QTimer* timer = new QTimer(this);
-    connect(timer, &QTimer::timeout, window(), &QQuickWindow::update);
+    connect(timer, &QTimer::timeout, this, &GameView::update);
     timer->start(100);
-
-    // window()->update();
   }
 }
 
 EngineInstance* GameView::engineInstance() const { return m_engineInstance; }
 
-void GameView::handleWindowChanged(QQuickWindow* win) {
-  if (win) {
-    connect(win, &QQuickWindow::beforeSynchronizing, this, &GameView::sync,
-            Qt::DirectConnection);
-    connect(win, &QQuickWindow::sceneGraphInvalidated, this, &GameView::cleanup,
-            Qt::DirectConnection);
-
-    win->setClearBeforeRendering(false);
-
-    m_windowChanged = true;
-  }
+QQuickFramebufferObject::Renderer* GameView::createRenderer() const {
+  return new GameViewRenderer{};
 }
 
-void GameView::cleanup() { m_renderer.reset(); }
-
 void GameView::setEngineInstance(EngineInstance* engineInstance) {
   if (m_engineInstance == engineInstance) {
     return;
   }
 
-  cleanup();
   m_engineInstance = engineInstance;
 
-  emit engineInstanceChanged(m_engineInstance);
+  Q_EMIT engineInstanceChanged(m_engineInstance);
 }
-
-void GameView::sync() {
-  if (!m_renderer && m_engineInstance) {
-    m_engineInstance->setOpenGLContext(window()->openglContext());
-    m_renderer.reset(new GameViewRenderer());
-    m_renderer->setEngineInstance(m_engineInstance);
-    connect(window(), &QQuickWindow::beforeRendering, m_renderer.data(),
-            &GameViewRenderer::paint, Qt::DirectConnection);
-  }
-
-  if (m_windowChanged || (m_viewportSize != size())) {
-    m_windowChanged = false;
-
-    if (m_engineInstance)
-      m_engineInstance->setOpenGLContext(window()->openglContext());
-
-    m_viewportSize = size().toSize();
-    m_centerPoint = QPoint(m_viewportSize.width(), m_viewportSize.height()) / 2;
-  }
-
-  if (m_engineInstance) {
-    const auto delta = mapFromGlobal(QCursor::pos()).toPoint() - m_centerPoint;
-
-    m_engineInstance->moveCamera(delta);
-
-    QCursor::setPos(window()->screen(), mapToGlobal(m_centerPoint).toPoint());
-  }
-
-  if (m_renderer) {
-    m_renderer->tick(m_delta);
-  }
-}
-
-GameViewRenderer::GameViewRenderer()
-    : QObject(), m_delta(0), m_engineInstance(nullptr) {}
-
-GameViewRenderer::~GameViewRenderer() {}
-
-void GameViewRenderer::tick(quint32 delta) { m_delta = delta; }
-
-void GameViewRenderer::setEngineInstance(EngineInstance* engineInstance) {
-  m_engineInstance = engineInstance;
-}
-
-void GameViewRenderer::paint() {
-  if (m_delta == 0) {
-    return;
-  }
-
-  if (m_engineInstance) {
-    m_engineInstance->advance(m_delta);
-    m_engineInstance->renderFrame();
-  }
-
-  // m_window->resetOpenGLState();
-}
-
-void GameViewRenderer::onViewportSizeChanged(QQuickWindow* window) {
-  if (m_engineInstance)
-    m_engineInstance->setOpenGLContext(window->openglContext());
-}
--- a/qmlfrontend/game_view.h	Fri Apr 05 13:10:55 2024 +0100
+++ b/qmlfrontend/game_view.h	Fri Apr 19 17:44:55 2024 +0200
@@ -1,34 +1,13 @@
 #ifndef GAMEVIEW_H
 #define GAMEVIEW_H
 
-#include <QQuickItem>
-
 #include <QPointer>
+#include <QQuickFramebufferObject>
 #include <QScopedPointer>
-#include <QtGui/QOpenGLFunctions>
-#include <QtGui/QOpenGLShaderProgram>
 
 #include "engine_instance.h"
 
-class GameViewRenderer : public QObject, protected QOpenGLFunctions {
-  Q_OBJECT
- public:
-  explicit GameViewRenderer();
-  ~GameViewRenderer() override;
-
-  void tick(quint32 delta);
-  void setEngineInstance(EngineInstance* engineInstance);
-
- public slots:
-  void paint();
-  void onViewportSizeChanged(QQuickWindow* window);
-
- private:
-  quint32 m_delta;
-  QPointer<EngineInstance> m_engineInstance;
-};
-
-class GameView : public QQuickItem {
+class GameView : public QQuickFramebufferObject {
   Q_OBJECT
 
   Q_PROPERTY(EngineInstance* engineInstance READ engineInstance WRITE
@@ -41,21 +20,16 @@
 
   EngineInstance* engineInstance() const;
 
- signals:
+  Renderer* createRenderer() const override;
+
+ Q_SIGNALS:
   void engineInstanceChanged(EngineInstance* engineInstance);
 
- public slots:
-  void sync();
-  void cleanup();
+ public Q_SLOTS:
   void setEngineInstance(EngineInstance* engineInstance);
 
- private slots:
-  void handleWindowChanged(QQuickWindow* win);
-
  private:
   quint32 m_delta;
-  QScopedPointer<GameViewRenderer> m_renderer;
-  bool m_windowChanged;
   QPointer<EngineInstance> m_engineInstance;
   QSize m_viewportSize;
   QPoint m_centerPoint;
--- a/qmlfrontend/hwengine.h	Fri Apr 05 13:10:55 2024 +0100
+++ b/qmlfrontend/hwengine.h	Fri Apr 19 17:44:55 2024 +0200
@@ -4,12 +4,11 @@
 #include <QList>
 #include <QObject>
 
-#include "engine_interface.h"
 #include "game_config.h"
+#include "preview_acceptor.h"
 
 class QQmlEngine;
 class EngineInstance;
-class PreviewAcceptor;
 
 class HWEngine : public QObject {
   Q_OBJECT
@@ -24,7 +23,7 @@
 
  public:
   explicit HWEngine(QObject* parent = nullptr);
-  ~HWEngine();
+  ~HWEngine() override;
 
   Q_INVOKABLE void getPreview();
   Q_INVOKABLE EngineInstance* runQuickGame();
--- a/qmlfrontend/main.cpp	Fri Apr 05 13:10:55 2024 +0100
+++ b/qmlfrontend/main.cpp	Fri Apr 19 17:44:55 2024 +0200
@@ -18,7 +18,6 @@
 }
 
 int main(int argc, char* argv[]) {
-  QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
   QGuiApplication app(argc, argv);
 
   QQmlApplicationEngine engine;
@@ -34,14 +33,15 @@
   qmlRegisterType<HWEngine>("Hedgewars.Engine", 1, 0, "HWEngine");
   qmlRegisterType<GameView>("Hedgewars.Engine", 1, 0, "GameView");
   qmlRegisterType<NetSession>("Hedgewars.Engine", 1, 0, "NetSession");
-  qmlRegisterUncreatableType<EngineInstance>("Hedgewars.Engine", 1, 0,
-                                             "EngineInstance",
-                                             "Create by HWEngine run methods");
+  qmlRegisterUncreatableType<EngineInstance>(
+      "Hedgewars.Engine", 1, 0, "EngineInstance",
+      QStringLiteral("Create by HWEngine run methods"));
 
   qmlRegisterUncreatableMetaObject(Engine::staticMetaObject, "Hedgewars.Engine",
-                                   1, 0, "Engine", "Namespace: only enums");
+                                   1, 0, "Engine",
+                                   QStringLiteral("Namespace: only enums"));
 
-  engine.load(QUrl(QLatin1String("qrc:/main.qml")));
+  engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
   if (engine.rootObjects().isEmpty()) return -1;
 
   return app.exec();
--- a/qmlfrontend/players_model.cpp	Fri Apr 05 13:10:55 2024 +0100
+++ b/qmlfrontend/players_model.cpp	Fri Apr 19 17:44:55 2024 +0200
@@ -23,7 +23,7 @@
 QVariant PlayersListModel::data(const QModelIndex &index, int role) const {
   if (!index.isValid() || index.row() < 0 || index.row() >= rowCount() ||
       index.column() != 0)
-    return QVariant(QVariant::Invalid);
+    return QVariant{};
 
   return m_data.at(index.row()).value(role);
 }
@@ -270,8 +270,8 @@
 }
 
 void PlayersListModel::updateSortData(const QModelIndex &index) {
-  QString result =
-      QString("%1%2%3%4%5%6")
+  const auto result =
+      QStringLiteral("%1%2%3%4%5%6")
           // room admins go first, then server admins, then friends
           .arg(1 - index.data(RoomAdmin).toInt())
           .arg(1 - index.data(ServerAdmin).toInt())
@@ -320,11 +320,12 @@
   if (!txt.open(QIODevice::ReadOnly)) return;
 
   QTextStream stream(&txt);
-  stream.setCodec("UTF-8");
 
   while (!stream.atEnd()) {
     QString str = stream.readLine();
-    if (str.startsWith(";") || str.isEmpty()) continue;
+    if (str.startsWith(';') || str.isEmpty()) {
+      continue;
+    }
 
     set.insert(str.trimmed());
   }
@@ -351,7 +352,6 @@
   if (!txt.open(QIODevice::WriteOnly | QIODevice::Truncate)) return;
 
   QTextStream stream(&txt);
-  stream.setCodec("UTF-8");
 
   stream << "; this list is used by Hedgewars - do not edit it unless you know "
             "what you're doing!"