QML OpenGL插件不以60Hz重绘

时间:2016-09-30 16:24:28

标签: c++ qt opengl qml

情况

我的公司有一个基于QML的应用程序,它使用基于OpenGL的自定义渲染插件(MyGame)显示一些内容。这个插件有一些关键需求:

  1. 为了响应基于QML的信号,能够在渲染器中实现更改 (例如,改变游戏渲染的对象的位置)
  2. 仅在MyGame的重绘循环中的特定位置处理这些更改 (这非常重要; MyGame对何时允许更改非常敏感。)
  3. 让插件重绘为60Hz(至少)。
  4. 问题

    我们现在的代码尊重(1)和(2),但是失败了(3);该插件不会始终如一地进行直观更新。 (更新是不稳定的,估计为5-10Hz。)我相信我们基于QQuickFramebufferObject创建的插件没有正确利用Qt / QML如何更新场景图。< / p>

    如何重新构建我的插件,以便获得以上所有三个?

    守则

    概述:

    • 该插件会创建QQuickFramebufferObjectMyPlugin)和QQuickFramebufferObject::RendererMyRenderer)。
    • 调用MyRenderer::render()后,它会调用MyGame::Redraw()本身,然后调用update()
    • MyGame::Redraw()执行所需操作,并且在可以接受更改的正确位置,在timeToMakeChanges上发出MyPlugin QML信号。
    • QML侦听onTimeToMakeChanges信号并调用插件上影响MyGame的方法。

    要解决低频视觉更新的问题,我发现如果我在我的插件上覆盖QML Canvas并使用Timer频繁重绘画布,我的插件就开始了在看起来大约60Hz的情况下进行视觉更新。显然,这是一个严重的黑客攻击。

    以下是代码设置的摘要。请原谅丢失/错误的代码;我正在尝试将数千行胶水代码提炼到这个问题的基本要素。

    MyPlugin.h

    #include <QOpenGLFramebufferObject>
    #include <QQuickFramebufferObject>
    
    class MyPlugin : public QQuickFramebufferObject {
    Q_OBJECT
    
    public:
      MyPlugin();
      virtual ~MyPlugin();
      virtual QQuickFramebufferObject::Renderer* createRenderer() const;
    
    signals:
      void timeToMakeChanges();
    
    public slots:
      void makeChanges(QVariant inValue);
      void HandleWindowChanged(QQuickWindow *inWindow);
    
    private:
      MyGame* GetGame() { ... }
    };
    

    MyPlugin.cpp

    #include "MyPlugin.h"
    #include <MyGame.h>
    
    // ******************************************************************
    
    class MyRenderer:
      public QObject,
      public QQuickFramebufferObject::Renderer,
      protected QOpenGLFunctions
    {
    Q_OBJECT
    
    public:
      virtual void render();
    
    private:
      static void RequestGameChanges();
    };
    
    void MyRenderer::render() {
    
      if ( !m_Initialized ) {
        QOpenGLFramebufferObject *theFbo = this->framebufferObject();
        InitializeGl( theFbo ); // Not shown
        m_MyGame = &MyGame::Create();
        m_MyGame->RegisterCallback(
          reinterpret_cast<qml_Function>(MyRenderer::RequestGameChanges)
        );
        m_Initialized = true;
      }
    
      m_MyGame->RestoreState();
      m_MyGame->Redraw();
      m_MyGame->SaveState();
      m_PluginItem->window()->resetOpenGLState();
    
      // Tell QML that we want to render again as soon as possible
      update();
    }
    
    // This gets invoked in the middle of m_MyGame->Redraw()
    void MyRenderer::RequestGameChanges() {
      emit m_PluginItem->timeToMakeChanges();
    }
    
    // ******************************************************************
    
    MyPlugin::MyPlugin() {
      setMirrorVertically(true);
      connect(
        this, SIGNAL(windowChanged(QQuickWindow*)),
        this, SLOT(HandleWindowChanged(QQuickWindow*))
      );
    }
    
    void MyPlugin::HandleWindowChanged(QQuickWindow *inWindow) {
      inWindow->setClearBeforeRendering(false);
    }
    
    void MyPlugin::makeChanges(QVariant inValue) {
      MyGame *theGame = GetGame();
      // Send the requested changes to theGame
    }
    
    QQuickFramebufferObject::Renderer* MyPlugin::createRenderer() const {
      m_Renderer = new MyRenderer( *this );
    }
    

    MyApp.qml

    import MyPlugin 1.0
    Window {
      MyPlugin {
        property var queuedUpChanges: ([])
        onSomeOtherSignal:   queueUpChangesToMake();
        onTimeToMakeChanges: makeChanges( queuedUpChanges );
      }
      Canvas { id:hack }
      Timer {
        interval:10; running:true; repeat:true
        onTriggered: hack.changeWhatYouShow();
      }
    }
    

    奖励积分

    主要问题是 “如何修改我的代码,以便获得60Hz更新?” 但是,如QML所示,上面的设置要求我排队QML中的所有更改,以便能够在MyGame::Render()的正确位置应用它们。

    理想情况下,我更愿意在没有timeToMakeChanges的情况下编写QML,例如:

    MyPlugin {
      onSomeOtherSignal: makeChanges( ... );
    }
    

    如果有办法实现这一目标(除了排队C ++中的更改) - 可能与synchronize()相关的事情我很想知道它。

1 个答案:

答案 0 :(得分:1)

我在QML中制作一个定时调用makeChanges的计时器。但是将所有州存储在MyPlugin中。然后,在Renderer::synchronize()中,从MyPlugin复制到MyRenderer,以便MyGame可以使用它。

(虽然,我不会在QML中做任何与游戏逻辑相关的计算)