Browse Source

Fix: defer OpenGL init until context is current; init via QQuickWindow::sceneGraphInitialized

Adam Djellouli 2 months ago
parent
commit
1ccdd545c9
6 changed files with 79 additions and 35 deletions
  1. 36 22
      main.cpp
  2. 10 4
      render/gl/buffer.cpp
  3. 7 3
      render/gl/mesh.cpp
  4. 12 3
      render/gl/renderer.cpp
  5. 6 1
      render/gl/shader.cpp
  6. 8 2
      render/gl/texture.cpp

+ 36 - 22
main.cpp

@@ -102,7 +102,7 @@ private:
 int main(int argc, char *argv[])
 {
     QGuiApplication app(argc, argv);
-    
+
     // Set up OpenGL 3.3 Core Profile
     QSurfaceFormat format;
     format.setVersion(3, 3);
@@ -111,45 +111,59 @@ int main(int argc, char *argv[])
     format.setStencilBufferSize(8);
     format.setSamples(4); // 4x MSAA
     QSurfaceFormat::setDefaultFormat(format);
-    
-    // Verify OpenGL context
-    QOpenGLContext context;
-    if (!context.create()) {
-        qFatal("Cannot create OpenGL context");
-    }
-    
-    qDebug() << "OpenGL Version:" << format.majorVersion() << "." << format.minorVersion();
-    qDebug() << "OpenGL Profile:" << (format.profile() == QSurfaceFormat::CoreProfile ? "Core" : "Compatibility");
-    
-    // Initialize game engine
-    GameEngine gameEngine;
-    gameEngine.initialize();
-    
+
     // Set up QML engine
     QQmlApplicationEngine engine;
-    
+
     // Register C++ types with QML if needed
     // qmlRegisterType<GameEngine>("StandardOfIron", 1, 0, "GameEngine");
-    
+
     const QUrl url(QStringLiteral("qrc:/ui/qml/Main.qml"));
-    
+
     QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
         &app, [url](QObject *obj, const QUrl &objUrl) {
             if (!obj && url == objUrl)
                 QCoreApplication::exit(-1);
         }, Qt::QueuedConnection);
-    
+
     // Load QML
     engine.load(url);
-    
+
     if (engine.rootObjects().isEmpty()) {
         qWarning() << "Failed to load QML file";
         return -1;
     }
-    
+
     qDebug() << "Application started successfully";
     qDebug() << "Assets directory:" << QDir::currentPath() + "/assets";
-    
+
+    // Get the QQuickWindow from the loaded QML
+    QObject* rootObj = engine.rootObjects().first();
+    QQuickWindow* window = qobject_cast<QQuickWindow*>(rootObj);
+    if (!window) {
+        // Try to find a QQuickWindow child
+        window = rootObj->findChild<QQuickWindow*>();
+    }
+
+    static GameEngine gameEngine;
+
+    if (window) {
+        // Connect to sceneGraphInitialized to safely initialize OpenGL resources
+        QObject::connect(window, &QQuickWindow::sceneGraphInitialized, &gameEngine, [&gameEngine]() {
+            gameEngine.initialize();
+            qDebug() << "Game engine initialized after scene graph.";
+        }, Qt::DirectConnection);
+
+        // Per-frame update/render loop
+        QObject::connect(window, &QQuickWindow::beforeRendering, &gameEngine, [&gameEngine]() {
+            gameEngine.update(1.0f / 60.0f); // Fixed timestep for now
+            gameEngine.render();
+        }, Qt::DirectConnection);
+        window->setClearBeforeRendering(false); // Let our renderer handle clearing
+    } else {
+        qWarning() << "No QQuickWindow found for OpenGL initialization.";
+    }
+
     return app.exec();
 }
 

+ 10 - 4
render/gl/buffer.cpp

@@ -3,8 +3,7 @@
 namespace Render::GL {
 
 Buffer::Buffer(Type type) : m_type(type) {
-    initializeOpenGLFunctions();
-    glGenBuffers(1, &m_buffer);
+    // Do not call GL without a current context; creation will be deferred to first bind
 }
 
 Buffer::~Buffer() {
@@ -14,6 +13,10 @@ Buffer::~Buffer() {
 }
 
 void Buffer::bind() {
+    if (!m_buffer) {
+        initializeOpenGLFunctions();
+        glGenBuffers(1, &m_buffer);
+    }
     glBindBuffer(getGLType(), m_buffer);
 }
 
@@ -46,8 +49,7 @@ GLenum Buffer::getGLUsage(Usage usage) const {
 
 // VertexArray implementation
 VertexArray::VertexArray() {
-    initializeOpenGLFunctions();
-    glGenVertexArrays(1, &m_vao);
+    // Defer creation until first bind when a context is current
 }
 
 VertexArray::~VertexArray() {
@@ -57,6 +59,10 @@ VertexArray::~VertexArray() {
 }
 
 void VertexArray::bind() {
+    if (!m_vao) {
+        initializeOpenGLFunctions();
+        glGenVertexArrays(1, &m_vao);
+    }
     glBindVertexArray(m_vao);
 }
 

+ 7 - 3
render/gl/mesh.cpp

@@ -5,13 +5,13 @@ namespace Render::GL {
 
 Mesh::Mesh(const std::vector<Vertex>& vertices, const std::vector<unsigned int>& indices)
     : m_vertices(vertices), m_indices(indices) {
-    initializeOpenGLFunctions();
-    setupBuffers();
+    // Defer GL buffer setup until a context is current; will be created on first draw
 }
 
 Mesh::~Mesh() = default;
 
 void Mesh::setupBuffers() {
+    initializeOpenGLFunctions();
     m_vao = std::make_unique<VertexArray>();
     m_vbo = std::make_unique<Buffer>(Buffer::Type::Vertex);
     m_ebo = std::make_unique<Buffer>(Buffer::Type::Index);
@@ -33,10 +33,14 @@ void Mesh::setupBuffers() {
 }
 
 void Mesh::draw() {
+    if (!m_vao) {
+        setupBuffers();
+    }
     m_vao->bind();
     
     // Draw elements
-    glDrawElements(GL_TRIANGLES, m_indices.size(), GL_UNSIGNED_INT, nullptr);
+    initializeOpenGLFunctions();
+    glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(m_indices.size()), GL_UNSIGNED_INT, nullptr);
     
     m_vao->unbind();
 }

+ 12 - 3
render/gl/renderer.cpp

@@ -2,12 +2,13 @@
 #include "../../engine/core/world.h"
 #include "../../engine/core/component.h"
 #include <QDebug>
+#include <QOpenGLContext>
 #include <algorithm>
 
 namespace Render::GL {
 
 Renderer::Renderer() {
-    initializeOpenGLFunctions();
+    // Defer OpenGL function initialization until a valid context is current
 }
 
 Renderer::~Renderer() {
@@ -15,6 +16,13 @@ Renderer::~Renderer() {
 }
 
 bool Renderer::initialize() {
+    // Ensure an OpenGL context is current before using any GL calls
+    if (!QOpenGLContext::currentContext()) {
+        qWarning() << "Renderer::initialize called without a current OpenGL context";
+        return false;
+    }
+
+    initializeOpenGLFunctions();
     // Enable depth testing
     glEnable(GL_DEPTH_TEST);
     glDepthFunc(GL_LESS);
@@ -214,13 +222,14 @@ bool Renderer::loadShaders() {
 
 void Renderer::createDefaultResources() {
     m_quadMesh = std::unique_ptr<Mesh>(createQuadMesh());
-    
+
     m_whiteTexture = std::make_unique<Texture>();
     m_whiteTexture->createEmpty(1, 1, Texture::Format::RGBA);
-    
+
     // Fill with white color
     unsigned char whitePixel[4] = {255, 255, 255, 255};
     m_whiteTexture->bind();
+    initializeOpenGLFunctions();
     glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, whitePixel);
 }
 

+ 6 - 1
render/gl/shader.cpp

@@ -6,7 +6,7 @@
 namespace Render::GL {
 
 Shader::Shader() {
-    initializeOpenGLFunctions();
+    // Defer OpenGL function initialization until a context is current
 }
 
 Shader::~Shader() {
@@ -34,6 +34,7 @@ bool Shader::loadFromFiles(const QString& vertexPath, const QString& fragmentPat
 }
 
 bool Shader::loadFromSource(const QString& vertexSource, const QString& fragmentSource) {
+    initializeOpenGLFunctions();
     GLuint vertexShader = compileShader(vertexSource, GL_VERTEX_SHADER);
     GLuint fragmentShader = compileShader(fragmentSource, GL_FRAGMENT_SHADER);
     
@@ -50,10 +51,12 @@ bool Shader::loadFromSource(const QString& vertexSource, const QString& fragment
 }
 
 void Shader::use() {
+    initializeOpenGLFunctions();
     glUseProgram(m_program);
 }
 
 void Shader::release() {
+    initializeOpenGLFunctions();
     glUseProgram(0);
 }
 
@@ -90,6 +93,7 @@ void Shader::setUniform(const QString& name, bool value) {
 }
 
 GLuint Shader::compileShader(const QString& source, GLenum type) {
+    initializeOpenGLFunctions();
     GLuint shader = glCreateShader(type);
     
     QByteArray sourceBytes = source.toUtf8();
@@ -111,6 +115,7 @@ GLuint Shader::compileShader(const QString& source, GLenum type) {
 }
 
 bool Shader::linkProgram(GLuint vertexShader, GLuint fragmentShader) {
+    initializeOpenGLFunctions();
     m_program = glCreateProgram();
     glAttachShader(m_program, vertexShader);
     glAttachShader(m_program, fragmentShader);

+ 8 - 2
render/gl/texture.cpp

@@ -5,8 +5,7 @@
 namespace Render::GL {
 
 Texture::Texture() {
-    initializeOpenGLFunctions();
-    glGenTextures(1, &m_texture);
+    // Defer creation until first bind when a context is current
 }
 
 Texture::~Texture() {
@@ -16,6 +15,7 @@ Texture::~Texture() {
 }
 
 bool Texture::loadFromFile(const QString& path) {
+    initializeOpenGLFunctions();
     QImage image;
     if (!image.load(path)) {
         qWarning() << "Failed to load texture:" << path;
@@ -46,6 +46,7 @@ bool Texture::loadFromFile(const QString& path) {
 }
 
 bool Texture::createEmpty(int width, int height, Format format) {
+    initializeOpenGLFunctions();
     m_width = width;
     m_height = height;
     m_format = format;
@@ -73,11 +74,16 @@ bool Texture::createEmpty(int width, int height, Format format) {
 }
 
 void Texture::bind(int unit) {
+    initializeOpenGLFunctions();
+    if (!m_texture) {
+        glGenTextures(1, &m_texture);
+    }
     glActiveTexture(GL_TEXTURE0 + unit);
     glBindTexture(GL_TEXTURE_2D, m_texture);
 }
 
 void Texture::unbind() {
+    initializeOpenGLFunctions();
     glBindTexture(GL_TEXTURE_2D, 0);
 }