Browse Source

fix gl window drawn on top of qml

djeada 2 months ago
parent
commit
92e102db05
8 changed files with 286 additions and 170 deletions
  1. 10 2
      CMakeLists.txt
  2. 126 0
      app/game_engine.cpp
  3. 53 0
      app/game_engine.h
  4. 8 162
      main.cpp
  5. 2 2
      render/gl/renderer.cpp
  6. 47 0
      ui/gl_view.cpp
  7. 35 0
      ui/gl_view.h
  8. 5 4
      ui/qml/GameView.qml

+ 10 - 2
CMakeLists.txt

@@ -39,9 +39,17 @@ add_subdirectory(tools)
 
 
 # ---- Executable ----
 # ---- Executable ----
 if(QT_VERSION_MAJOR EQUAL 6)
 if(QT_VERSION_MAJOR EQUAL 6)
-    qt6_add_executable(standard_of_iron main.cpp)
+    qt6_add_executable(standard_of_iron
+        main.cpp
+        app/game_engine.cpp
+        ui/gl_view.cpp
+    )
 else()
 else()
-    add_executable(standard_of_iron main.cpp)
+    add_executable(standard_of_iron
+        main.cpp
+        app/game_engine.cpp
+        ui/gl_view.cpp
+    )
 endif()
 endif()
 
 
 # ---- QML module ----
 # ---- QML module ----

+ 126 - 0
app/game_engine.cpp

@@ -0,0 +1,126 @@
+#include "game_engine.h"
+
+#include <QQuickWindow>
+#include <QOpenGLContext>
+#include <QDebug>
+
+#include "engine/core/world.h"
+#include "engine/core/component.h"
+#include "render/gl/renderer.h"
+#include "render/gl/camera.h"
+#include "game/systems/movement_system.h"
+#include "game/systems/combat_system.h"
+#include "game/systems/selection_system.h"
+
+GameEngine::GameEngine() {
+    m_world    = std::make_unique<Engine::Core::World>();
+    m_renderer = std::make_unique<Render::GL::Renderer>();
+    m_camera   = std::make_unique<Render::GL::Camera>();
+
+    m_world->addSystem(std::make_unique<Game::Systems::MovementSystem>());
+    m_world->addSystem(std::make_unique<Game::Systems::CombatSystem>());
+    m_world->addSystem(std::make_unique<Game::Systems::AISystem>());
+
+    m_selectionSystem = std::make_unique<Game::Systems::SelectionSystem>();
+    m_world->addSystem(std::make_unique<Game::Systems::SelectionSystem>());
+
+    setupTestScene();
+}
+
+GameEngine::~GameEngine() = default;
+
+void GameEngine::onMapClicked(qreal sx, qreal sy) {
+    if (!m_window) return;
+    ensureInitialized();
+    QVector3D hit;
+    if (!screenToGround(QPointF(sx, sy), hit)) return;
+    if (auto* entity = m_world->getEntity(m_playerUnitId)) {
+        if (auto* move = entity->getComponent<Engine::Core::MovementComponent>()) {
+            move->targetX = hit.x();
+            move->targetY = hit.z();
+            move->hasTarget = true;
+        }
+    }
+}
+
+void GameEngine::initialize() {
+    QOpenGLContext* ctx = QOpenGLContext::currentContext();
+    if (!ctx || !ctx->isValid()) {
+        qWarning() << "GameEngine::initialize called without a current, valid OpenGL context";
+        return;
+    }
+    if (!m_renderer->initialize()) {
+        qWarning() << "Failed to initialize renderer";
+        return;
+    }
+    m_renderer->setCamera(m_camera.get());
+    m_camera->setRTSView(QVector3D(0, 0, 0), 15.0f, 45.0f);
+    m_camera->setPerspective(45.0f, 16.0f/9.0f, 0.1f, 1000.0f);
+    m_initialized = true;
+}
+
+void GameEngine::ensureInitialized() { if (!m_initialized) initialize(); }
+
+void GameEngine::update(float dt) {
+    if (m_world) m_world->update(dt);
+}
+
+void GameEngine::render(int pixelWidth, int pixelHeight) {
+    if (!m_renderer || !m_world || !m_initialized) return;
+    if (pixelWidth > 0 && pixelHeight > 0) {
+        m_renderer->setViewport(pixelWidth, pixelHeight);
+    }
+    m_renderer->beginFrame();
+    m_renderer->renderWorld(m_world.get());
+    m_renderer->endFrame();
+}
+
+void GameEngine::setupTestScene() {
+    auto entity = m_world->createEntity();
+    m_playerUnitId = entity->getId();
+
+    auto transform = entity->addComponent<Engine::Core::TransformComponent>();
+    transform->position = {0.0f, 0.0f, 0.0f};
+    transform->scale    = {0.5f, 0.5f, 0.5f};
+    transform->rotation.x = -90.0f;
+
+    auto renderable = entity->addComponent<Engine::Core::RenderableComponent>("", "");
+    renderable->visible = true;
+
+    auto unit = entity->addComponent<Engine::Core::UnitComponent>();
+    unit->unitType = "archer";
+    unit->health = 80;
+    unit->maxHealth = 80;
+    unit->speed = 3.0f;
+
+    entity->addComponent<Engine::Core::MovementComponent>();
+}
+
+bool GameEngine::screenToGround(const QPointF& screenPt, QVector3D& outWorld) {
+    if (!m_window || !m_camera) return false;
+    float w = float(m_window->width());
+    float h = float(m_window->height());
+    if (w <= 0 || h <= 0) return false;
+
+    float x = (2.0f * float(screenPt.x()) / w) - 1.0f;
+    float y = 1.0f - (2.0f * float(screenPt.y()) / h);
+
+    bool ok = false;
+    QMatrix4x4 invVP = (m_camera->getProjectionMatrix() * m_camera->getViewMatrix()).inverted(&ok);
+    if (!ok) return false;
+
+    QVector4D nearClip(x, y, 0.0f, 1.0f);
+    QVector4D farClip (x, y, 1.0f, 1.0f);
+    QVector4D nearWorld4 = invVP * nearClip;
+    QVector4D farWorld4  = invVP * farClip;
+    if (nearWorld4.w() == 0.0f || farWorld4.w() == 0.0f) return false;
+    QVector3D rayOrigin = (nearWorld4 / nearWorld4.w()).toVector3D();
+    QVector3D rayEnd    = (farWorld4  / farWorld4.w()).toVector3D();
+    QVector3D rayDir    = (rayEnd - rayOrigin).normalized();
+
+    if (qFuzzyIsNull(rayDir.y())) return false;
+    float t = -rayOrigin.y() / rayDir.y();
+    if (t < 0.0f) return false;
+    outWorld = rayOrigin + rayDir * t;
+    return true;
+}

+ 53 - 0
app/game_engine.h

@@ -0,0 +1,53 @@
+#pragma once
+
+#include <QObject>
+#include <QVector3D>
+#include <QMatrix4x4>
+#include <QPointF>
+#include <memory>
+
+namespace Engine { namespace Core {
+class World;
+using EntityID = unsigned int;
+struct MovementComponent;
+struct TransformComponent;
+struct RenderableComponent;
+} }
+
+namespace Render { namespace GL {
+class Renderer;
+class Camera;
+} }
+
+namespace Game { namespace Systems { class SelectionSystem; } }
+
+class QQuickWindow;
+
+class GameEngine : public QObject {
+    Q_OBJECT
+public:
+    GameEngine();
+    ~GameEngine();
+
+    Q_INVOKABLE void onMapClicked(qreal sx, qreal sy);
+
+    void setWindow(QQuickWindow* w) { m_window = w; }
+
+    // Render-thread friendly calls (must be invoked when a valid GL context is current)
+    void ensureInitialized();
+    void update(float dt);
+    void render(int pixelWidth, int pixelHeight);
+
+private:
+    void initialize();
+    void setupTestScene();
+    bool screenToGround(const QPointF& screenPt, QVector3D& outWorld);
+
+    std::unique_ptr<Engine::Core::World> m_world;
+    std::unique_ptr<Render::GL::Renderer> m_renderer;
+    std::unique_ptr<Render::GL::Camera>   m_camera;
+    std::unique_ptr<Game::Systems::SelectionSystem> m_selectionSystem;
+    QQuickWindow* m_window = nullptr;
+    Engine::Core::EntityID m_playerUnitId = 0;
+    bool m_initialized = false;
+};

+ 8 - 162
main.cpp

@@ -1,3 +1,4 @@
+// System/Qt headers
 #include <QGuiApplication>
 #include <QGuiApplication>
 #include <QQmlApplicationEngine>
 #include <QQmlApplicationEngine>
 #include <QOpenGLContext>
 #include <QOpenGLContext>
@@ -8,149 +9,9 @@
 #include <QQmlContext>
 #include <QQmlContext>
 #include <QSGRendererInterface>
 #include <QSGRendererInterface>
 
 
-#include "engine/core/world.h"
-#include "engine/core/component.h"
-#include "render/gl/renderer.h"
-#include "render/gl/camera.h"
-#include "game/systems/movement_system.h"
-#include "game/systems/combat_system.h"
-#include "game/systems/selection_system.h"
-// #include "game/systems/ai_system.h" // keep if you have AISystem; remove if not
-
-class GameEngine : public QObject {
-    Q_OBJECT
-public:
-    GameEngine() {
-        m_world    = std::make_unique<Engine::Core::World>();
-        m_renderer = std::make_unique<Render::GL::Renderer>();
-        m_camera   = std::make_unique<Render::GL::Camera>();
-
-        m_world->addSystem(std::make_unique<Game::Systems::MovementSystem>());
-        m_world->addSystem(std::make_unique<Game::Systems::CombatSystem>());
-        m_world->addSystem(std::make_unique<Game::Systems::AISystem>());
-
-        m_selectionSystem = std::make_unique<Game::Systems::SelectionSystem>();
-        m_world->addSystem(std::make_unique<Game::Systems::SelectionSystem>());
-
-        setupTestScene();
-    }
-
-    Q_INVOKABLE void onMapClicked(qreal sx, qreal sy) {
-        if (!m_window) return;
-        ensureInitialized();
-        QVector3D hit;
-        if (!screenToGround(QPointF(sx, sy), hit)) return;
-        if (auto* entity = m_world->getEntity(m_playerUnitId)) {
-            if (auto* move = entity->getComponent<Engine::Core::MovementComponent>()) {
-                move->targetX = hit.x();
-                move->targetY = hit.z();
-                move->hasTarget = true;
-            }
-        }
-    }
-
-    void setWindow(QQuickWindow* w) { m_window = w; }
-
-    void initialize() {
-        // Must be called with a current, valid GL context.
-        QOpenGLContext* ctx = QOpenGLContext::currentContext();
-        if (!ctx || !ctx->isValid()) {
-            qWarning() << "GameEngine::initialize called without a current, valid OpenGL context";
-            return;
-        }
-        if (!m_renderer->initialize()) {
-            qWarning() << "Failed to initialize renderer";
-            return;
-        }
-        m_renderer->setCamera(m_camera.get());
-        m_camera->setRTSView(QVector3D(0, 0, 0), 15.0f, 45.0f);
-        m_camera->setPerspective(45.0f, 16.0f/9.0f, 0.1f, 1000.0f);
-
-        m_initialized = true;
-        qDebug() << "Game engine initialized successfully";
-    }
-
-    void ensureInitialized() { if (!m_initialized) initialize(); }
-
-    void update(float dt) {
-        if (m_world) m_world->update(dt);
-    }
-
-    void render() {
-        if (!m_renderer || !m_world || !m_initialized) return;
-
-        if (m_window) {
-            const int vpW = int(m_window->width()  * m_window->effectiveDevicePixelRatio());
-            const int vpH = int(m_window->height() * m_window->effectiveDevicePixelRatio());
-            m_renderer->setViewport(vpW, vpH);
-        }
-
-        m_renderer->beginFrame();
-        m_renderer->renderWorld(m_world.get());
-        m_renderer->endFrame();
-    }
-
-private:
-    void setupTestScene() {
-        auto entity = m_world->createEntity();
-        m_playerUnitId = entity->getId();
-
-        auto transform = entity->addComponent<Engine::Core::TransformComponent>();
-        transform->position = {0.0f, 0.0f, 0.0f};
-        transform->scale    = {0.5f, 0.5f, 0.5f};
-        transform->rotation.x = -90.0f; // lay quad on XZ plane
-
-        auto renderable = entity->addComponent<Engine::Core::RenderableComponent>("", "");
-        renderable->visible = true;
-
-        auto unit = entity->addComponent<Engine::Core::UnitComponent>();
-        unit->unitType = "archer";
-        unit->health = 80;
-        unit->maxHealth = 80;
-        unit->speed = 3.0f;
-
-        entity->addComponent<Engine::Core::MovementComponent>();
-
-        qDebug() << "Test scene created with 1 archer (entity" << m_playerUnitId << ")";
-    }
-
-    bool screenToGround(const QPointF& screenPt, QVector3D& outWorld) {
-        if (!m_window || !m_camera) return false;
-        float w = float(m_window->width());
-        float h = float(m_window->height());
-        if (w <= 0 || h <= 0) return false;
-
-        float x = (2.0f * float(screenPt.x()) / w) - 1.0f;
-        float y = 1.0f - (2.0f * float(screenPt.y()) / h);
-
-        bool ok = false;
-        QMatrix4x4 invVP = (m_camera->getProjectionMatrix() * m_camera->getViewMatrix()).inverted(&ok);
-        if (!ok) return false;
-
-        QVector4D nearClip(x, y, 0.0f, 1.0f);
-        QVector4D farClip (x, y, 1.0f, 1.0f);
-        QVector4D nearWorld4 = invVP * nearClip;
-        QVector4D farWorld4  = invVP * farClip;
-        if (nearWorld4.w() == 0.0f || farWorld4.w() == 0.0f) return false;
-        QVector3D rayOrigin = (nearWorld4 / nearWorld4.w()).toVector3D();
-        QVector3D rayEnd    = (farWorld4  / farWorld4.w()).toVector3D();
-        QVector3D rayDir    = (rayEnd - rayOrigin).normalized();
-
-        if (qFuzzyIsNull(rayDir.y())) return false;
-        float t = -rayOrigin.y() / rayDir.y();
-        if (t < 0.0f) return false;
-        outWorld = rayOrigin + rayDir * t;
-        return true;
-    }
-
-    std::unique_ptr<Engine::Core::World> m_world;
-    std::unique_ptr<Render::GL::Renderer> m_renderer;
-    std::unique_ptr<Render::GL::Camera>   m_camera;
-    std::unique_ptr<Game::Systems::SelectionSystem> m_selectionSystem;
-    QQuickWindow* m_window = nullptr;
-    Engine::Core::EntityID m_playerUnitId = 0;
-    bool m_initialized = false;
-};
+// App headers
+#include "app/game_engine.h"
+#include "ui/gl_view.h"
 
 
 int main(int argc, char *argv[])
 int main(int argc, char *argv[])
 {
 {
@@ -178,6 +39,8 @@ int main(int argc, char *argv[])
     QQmlApplicationEngine engine;
     QQmlApplicationEngine engine;
     // Expose to QML BEFORE loading (safer if QML binds early).
     // Expose to QML BEFORE loading (safer if QML binds early).
     engine.rootContext()->setContextProperty("game", gameEngine);
     engine.rootContext()->setContextProperty("game", gameEngine);
+    // Register our GLView item so QML can embed the GL scene inside the scene graph
+    qmlRegisterType<GLView>("StandardOfIron", 1, 0, "GLView");
     engine.load(QUrl(QStringLiteral("qrc:/StandardOfIron/ui/qml/Main.qml")));
     engine.load(QUrl(QStringLiteral("qrc:/StandardOfIron/ui/qml/Main.qml")));
     if (engine.rootObjects().isEmpty()) {
     if (engine.rootObjects().isEmpty()) {
         qWarning() << "Failed to load QML file";
         qWarning() << "Failed to load QML file";
@@ -192,8 +55,7 @@ int main(int argc, char *argv[])
         return -2;
         return -2;
     }
     }
 
 
-    // For now, draw GL ON TOP of QML (simplest to verify).
-    window->setColor(Qt::black);
+    // Let Qt Quick manage the clear; keep window color default.
 
 
     gameEngine->setWindow(window);
     gameEngine->setWindow(window);
 
 
@@ -216,22 +78,6 @@ int main(int argc, char *argv[])
         app.exit(3);
         app.exit(3);
     });
     });
 
 
-    // Draw AFTER Qt Quick so GL is visible on top.
-    QObject::connect(window, &QQuickWindow::afterRendering, gameEngine, [gameEngine, window]() {
-        auto *ri = window->rendererInterface();
-        const bool isGL = ri && ri->graphicsApi() == QSGRendererInterface::OpenGLRhi;
-        if (!isGL) return;
-
-        window->beginExternalCommands(); // makes the GL context current for external GL
-        QOpenGLContext* ctx = QOpenGLContext::currentContext();
-        if (!ctx || !ctx->isValid()) { window->endExternalCommands(); return; }
-
-        gameEngine->ensureInitialized();
-        gameEngine->update(1.0f / 60.0f);
-        gameEngine->render();
-
-        window->endExternalCommands();
-    }, Qt::DirectConnection);
 
 
     qDebug() << "Application started successfully";
     qDebug() << "Application started successfully";
     qDebug() << "Assets directory:" << QDir::currentPath() + "/assets";
     qDebug() << "Assets directory:" << QDir::currentPath() + "/assets";
@@ -239,5 +85,5 @@ int main(int argc, char *argv[])
     return app.exec();
     return app.exec();
 }
 }
 
 
-#include "main.moc"
+// no Q_OBJECT in this TU anymore
 
 

+ 2 - 2
render/gl/renderer.cpp

@@ -31,8 +31,8 @@ bool Renderer::initialize() {
     glEnable(GL_BLEND);
     glEnable(GL_BLEND);
     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
     
     
-    // Set default clear color
-    setClearColor(0.2f, 0.3f, 0.3f, 1.0f);
+    // Set default clear color with alpha 0 to allow QML overlay compositing
+    setClearColor(0.2f, 0.3f, 0.3f, 0.0f);
     
     
     if (!loadShaders()) {
     if (!loadShaders()) {
         return false;
         return false;

+ 47 - 0
ui/gl_view.cpp

@@ -0,0 +1,47 @@
+#include "gl_view.h"
+#include "../app/game_engine.h"
+
+#include <QOpenGLFramebufferObject>
+#include <QOpenGLFramebufferObjectFormat>
+#include <QQuickWindow>
+
+GLView::GLView() {
+    setMirrorVertically(true); // typical for FBO backed items
+}
+
+QQuickFramebufferObject::Renderer* GLView::createRenderer() const {
+    return new GLRenderer(m_engine);
+}
+
+QObject* GLView::engine() const { return m_engine; }
+
+void GLView::setEngine(QObject* eng) {
+    if (m_engine == eng) return;
+    m_engine = qobject_cast<GameEngine*>(eng);
+    emit engineChanged();
+    update();
+}
+
+GLView::GLRenderer::GLRenderer(QPointer<GameEngine> engine)
+    : m_engine(engine) {}
+
+void GLView::GLRenderer::render() {
+    if (!m_engine) return;
+    m_engine->ensureInitialized();
+    m_engine->update(1.0f/60.0f);
+    m_engine->render(m_size.width(), m_size.height());
+    update(); // schedule next frame
+}
+
+QOpenGLFramebufferObject* GLView::GLRenderer::createFramebufferObject(const QSize &size) {
+    m_size = size;
+    QOpenGLFramebufferObjectFormat fmt;
+    fmt.setAttachment(QOpenGLFramebufferObject::Depth);
+    fmt.setSamples(0);
+    return new QOpenGLFramebufferObject(size, fmt);
+}
+
+void GLView::GLRenderer::synchronize(QQuickFramebufferObject *item) {
+    auto *view = static_cast<GLView*>(item);
+    m_engine = qobject_cast<GameEngine*>(view->engine());
+}

+ 35 - 0
ui/gl_view.h

@@ -0,0 +1,35 @@
+#pragma once
+
+#include <QQuickFramebufferObject>
+#include <QPointer>
+
+class GameEngine;
+
+class GLView : public QQuickFramebufferObject {
+    Q_OBJECT
+public:
+    GLView();
+
+    Renderer* createRenderer() const override;
+
+    Q_PROPERTY(QObject* engine READ engine WRITE setEngine NOTIFY engineChanged)
+    QObject* engine() const;
+    void setEngine(QObject* eng);
+
+signals:
+    void engineChanged();
+
+private:
+    QPointer<GameEngine> m_engine;
+
+    class GLRenderer : public QQuickFramebufferObject::Renderer {
+    public:
+        explicit GLRenderer(QPointer<GameEngine> engine);
+        void render() override;
+        QOpenGLFramebufferObject* createFramebufferObject(const QSize &size) override;
+        void synchronize(QQuickFramebufferObject *item) override;
+    private:
+        QPointer<GameEngine> m_engine;
+        QSize m_size;
+    };
+};

+ 5 - 4
ui/qml/GameView.qml

@@ -1,5 +1,6 @@
 import QtQuick 2.15
 import QtQuick 2.15
 import QtQuick.Controls 2.15
 import QtQuick.Controls 2.15
+import StandardOfIron 1.0
 
 
 Item {
 Item {
     id: gameView
     id: gameView
@@ -25,11 +26,11 @@ Item {
         // Handle unit commands
         // Handle unit commands
     }
     }
     
     
-    // OpenGL rendering area
-        Rectangle {
+    // OpenGL rendering item inside scene graph
+        GLView {
         id: renderArea
         id: renderArea
         anchors.fill: parent
         anchors.fill: parent
-            color: "transparent"
+        engine: game // GameEngine object exposed from C++
         
         
         // Placeholder text (disabled by default to not cover GL)
         // Placeholder text (disabled by default to not cover GL)
         // Text {
         // Text {
@@ -40,7 +41,7 @@ Item {
         //     horizontalAlignment: Text.AlignHCenter
         //     horizontalAlignment: Text.AlignHCenter
         // }
         // }
         
         
-        // Camera controls info
+        // Camera controls info overlays
         Rectangle {
         Rectangle {
             anchors.top: parent.top
             anchors.top: parent.top
             anchors.left: parent.left
             anchors.left: parent.left