2017-01-16 5 views
1

Bisher Ich verstehe, dass wir zwei Threads in QML, unser Hauptanwendungsthread, und unsere „Szenengraph“ Thread: http://doc.qt.io/qt-5/qtquick-visualcanvas-scenegraph.htmlWie programmgesteuert ein vtk-Objekt in qml rendern?

ich meine eigene vtkQmlItem mit Hilfe dieser Verbindung implementiert haben: http://doc.qt.io/qt-5/qtquick-scenegraph-openglunderqml-example.html

und ich habe festgestellt, dass meine vtkscene nur gerendert wird, wenn das Signal afterrendering durch den qml-Fluss ausgegeben wird.

Bisher ist alles in Ordnung und funktioniert perfekt, ich meine VTK Szene sehen und sogar interract damit kann.

Aber ich mag auch programmatisch als auch meine VTK Szene machen, da ich durch Bewegen der Kamera um ein VTK Objekt eine Animation machen will.

Aufruf renderer->render() zeigt direkt eine Menge vtk Fehler, und scheint nicht der gute Weg, dies zu tun.

Aufruf this->window()->update() scheint das Ereignis in der Ereignisschleife zu setzen, wenn ich will es sofort behandelt werden. Die einzige Möglichkeit, um es sofort zum Laufen zu bringen, ist die Verwendung von QApplication :: processEvents(), was ein Hack ist, den ich nicht mag und der eine andere Lösung mag.

So ist die Pseudo-Code der Arbeitslösung, die Ich mag nicht wissen, ist die folgende:

for (int i = 0; i < 50; i++) 
{ 
    ChangeCameraPosition(i); // Change the position and orientation of the vtk camera 
    this->window()->update(); 
    QApplication::processEvents(); // The hack I don't like 
    QThread::msleep(500); 
} 

Antwort

2

das Problem ist eigentlich ein bisschen kompliziert und wenn nichts in den letzten Monaten verändert, gibt es noch keine Unterstützung für QtQuick in VTK, was bedeutet, keine einfache paar Zeilen Lösung. Sie können Supportklassen für QtWidgets in VTK/GUISupport/QtOpenGL/finden und sie als Vorlage verwenden, um Unterstützung für qml abzuleiten. Aber hauptsächlich empfehle ich die Überprüfung this thread for a discussion about this topic.

Der Schlüsselpunkt ist, dass QtQuick den OpenGL-Kontext für das Qml-Fenster enthält, in dem Sie versuchen, in einem dedizierten Thread zu rendern, und es wird nichts anderes diesen Kontext erhalten lassen. Um es von VTK in es zu rendern, müssen Sie es innerhalb dieses Threads tun. Das bedeutet:

1) Erstellen Sie Ihr eigenes vtkRenderWindow, das die Render() - Methode überschreibt, so dass sichergestellt ist, dass es im Rendering-Thread von qml passiert.

2) Sie sich, dass machen Fenster in einem Framebuffer-Objekt durch die qtquick (Instanz QQuickFramebufferObject) vorgesehen machen.

3) Wiedergabesignale, die Verbindungen mit der vtk Rendering Methoden des qt -> z.B. Wenn das vtk-Renderfenster makeCurrent aufruft, wird der Rendering-Thread des qt "aufgeweckt".

Hier ist meine Implementierung basiert auf Taylor Braun-Jones' template oben verbunden. Es ist vielleicht nicht perfekt, aber es funktioniert für mich (Ich habe einige Teile meiner App entfernt, so dass es nicht sofort kompilieren könnte, aber es sollte Sie auf einen Weg zu einer funktionierenden Lösung bringen):

qmlVtk.h :

#include <vtkEventQtSlotConnect.h> 
#include <vtkGenericOpenGLRenderWindow.h> 
#include <vtkRenderer.h> 

#include <QtQuick/QQuickFramebufferObject> 
// Use the OpenGL API abstraction from Qt instead of from VTK because vtkgl.h 
// and other Qt OpenGL-related headers do not play nice when included in the 
// same compilation unit 
#include <QOpenGLFunctions> 

#include <qqmlapplicationengine.h> 

class QVTKFramebufferObjectRenderer; 
class QVTKInteractorAdapter; 
class vtkInternalOpenGLRenderWindow; 
class QVTKFramebufferObjectRenderer; 


class QVTKFrameBufferObjectItem : public QQuickFramebufferObject 
{ 
    Q_OBJECT 

public: 
    QVTKFrameBufferObjectItem(QQuickItem *parent = 0); 
    ~QVTKFrameBufferObjectItem(); 
    Renderer *createRenderer() const; 
    vtkSmartPointer<vtkInternalOpenGLRenderWindow> GetRenderWindow() const; 

protected: 
    // Called once before the FBO is created for the first time. This method is 
    // called from render thread while the GUI thread is blocked. 
    virtual void init(); 

    vtkSmartPointer<vtkInternalOpenGLRenderWindow> m_win; 
    QVTKInteractorAdapter* m_irenAdapter; 
    vtkSmartPointer<vtkEventQtSlotConnect> mConnect; 

    friend class QVTKFramebufferObjectRenderer; 

    // Convert the position of the event from openGL coordinate to native coordinate 
    QMouseEvent openGLToNative(QMouseEvent const& event); 

    virtual void mouseMoveEvent(QMouseEvent * event); 
    virtual void mousePressEvent(QMouseEvent * event); 
    virtual void mouseReleaseEvent(QMouseEvent * event); 
    virtual void mouseDoubleClickEvent(QMouseEvent * event); 
    virtual void wheelEvent(QWheelEvent *event); 
    virtual void keyPressEvent(QKeyEvent* event); 
    virtual void keyReleaseEvent(QKeyEvent* event); 
    virtual void focusInEvent(QFocusEvent * event); 
    virtual void focusOutEvent(QFocusEvent * event); 


    protected Q_SLOTS: 
    // slot to make this vtk render window current 
    virtual void MakeCurrent(); 
    // slot called when vtk wants to know if the context is current 
    virtual void IsCurrent(vtkObject* caller, unsigned long vtk_event, void* client_data, void* call_data); 
    // slot called when vtk wants to start the render 
    virtual void Start(); 
    // slot called when vtk wants to end the render 
    virtual void End(); 
    // slot called when vtk wants to know if a window is direct 
    virtual void IsDirect(vtkObject* caller, unsigned long vtk_event, void* client_data, void* call_data); 
    // slot called when vtk wants to know if a window supports OpenGL 
    virtual void SupportsOpenGL(vtkObject* caller, unsigned long vtk_event, void* client_data, void* call_data); 
}; 

/// <summary> 
/// An extension of vktGenericOpenGLRenderWindow to work with Qt. 
/// Serves to write VTK-generated render calls to a framebuffer provided and maintained by Qt. It is meant to be used within Qt render loop, i.e. using Qt's render thread. 
/// </summary> 
/// <seealso cref="vtkGenericOpenGLRenderWindow" /> 
/// <seealso cref="QOpenGLFunctions" /> 
class vtkInternalOpenGLRenderWindow : public vtkGenericOpenGLRenderWindow, protected QOpenGLFunctions 
{ 

public: 
    static vtkInternalOpenGLRenderWindow* New(); 
    vtkTypeMacro(vtkInternalOpenGLRenderWindow, vtkGenericOpenGLRenderWindow) 

    virtual void OpenGLInitState(); 

    // Override to use deferred rendering - Tell the QSG that we need to 
    // be rendered which will then, at the appropriate time, call 
    // InternalRender to do the actual OpenGL rendering. 
    virtual void Render(); 

    // Do the actual OpenGL rendering 
    void InternalRender(); 

    // Provides a convenient way to set the protected FBO ivars from an existing 
    // FBO that was created and owned by Qt's FBO abstraction class 
    // QOpenGLFramebufferObject 
    void SetFramebufferObject(QOpenGLFramebufferObject *fbo); 

    QVTKFramebufferObjectRenderer *QtParentRenderer; 

protected: 
    vtkInternalOpenGLRenderWindow(); 

    ~vtkInternalOpenGLRenderWindow() 
    { 
     // Prevent superclass destructors from destroying the framebuffer object. 
     // QOpenGLFramebufferObject owns the FBO and manages it's lifecyle. 
     this->OffScreenRendering = 0; 
    } 
}; 

qmlVtk.cpp:

#include "QVTKFramebufferObjectItem.h" 

#include <QQuickFramebufferObject> 
#include <QQuickWindow> 
#include <QOpenGLFramebufferObject> 
#include <QVTKInteractorAdapter.h> 

#include <vtkRenderWindowInteractor.h> 
#include <vtkObjectFactory.h> 

#include <vtkSmartPointer.h> 
#include <vtkCamera.h> 
#include <vtkProperty.h> 

#include <qglfunctions.h> 


class QVTKFramebufferObjectRenderer : public QQuickFramebufferObject::Renderer 
{ 
    friend class vtkInternalOpenGLRenderWindow; 

public: 
    QVTKFramebufferObjectRenderer(vtkSmartPointer<vtkInternalOpenGLRenderWindow> rw) : 
     m_framebufferObject(0) 
    { 
     m_vtkRenderWindow = rw; 

     m_vtkRenderWindow->QtParentRenderer = this; 
    } 

    ~QVTKFramebufferObjectRenderer() 
    { 
     m_vtkRenderWindow->QtParentRenderer = 0; 
     glFrontFace(GL_CCW); // restore default settings 
    } 

    virtual void synchronize(QQuickFramebufferObject * item) 
    { 
     // the first synchronize call - right before the the framebufferObject 
     // is created for the first time 
     if (!m_framebufferObject) 
     { 
      QVTKFrameBufferObjectItem *vtkItem = static_cast<QVTKFrameBufferObjectItem*>(item); 
      vtkItem->init(); 
     } 
    } 

    virtual void render() 
    { 
     m_vtkRenderWindow->InternalRender(); // vtkXOpenGLRenderWindow renders the scene to the FBO 
    } 

    QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) 
    { 
     QOpenGLFramebufferObjectFormat format; 
     format.setAttachment(QOpenGLFramebufferObject::Depth); 
     m_framebufferObject = new QOpenGLFramebufferObject(size, format); 

     m_vtkRenderWindow->SetFramebufferObject(m_framebufferObject); 

     return m_framebufferObject; 
    } 

    vtkSmartPointer<vtkInternalOpenGLRenderWindow> m_vtkRenderWindow; 
    QOpenGLFramebufferObject *m_framebufferObject; 
}; 

vtkStandardNewMacro(vtkInternalOpenGLRenderWindow); 

vtkInternalOpenGLRenderWindow::vtkInternalOpenGLRenderWindow() : 
QtParentRenderer(0) 
{ 
    vtkOpenGLRenderWindow::OpenGLInitContext(); 
} 

void vtkInternalOpenGLRenderWindow::OpenGLInitState() 
{ 
    this->MakeCurrent(); 
    vtkOpenGLRenderWindow::OpenGLInitState(); 
    // Before any of the gl* functions in QOpenGLFunctions are called for a 
    // given OpenGL context, an initialization must be run within that context 
    initializeOpenGLFunctions(); 
    glFrontFace(GL_CW); // to compensate for the switched Y axis 
} 

void vtkInternalOpenGLRenderWindow::InternalRender() 
{ 
    vtkOpenGLRenderWindow::Render(); 
} 

// 
// vtkInternalOpenGLRenderWindow Definitions 
// 

void vtkInternalOpenGLRenderWindow::Render() 
{ 
    this->QtParentRenderer->update(); 
} 

void vtkInternalOpenGLRenderWindow::SetFramebufferObject(QOpenGLFramebufferObject *fbo) 
{ 
    // QOpenGLFramebufferObject documentation states that "The color render 
    // buffer or texture will have the specified internal format, and will 
    // be bound to the GL_COLOR_ATTACHMENT0 attachment in the framebuffer 
    // object" 
    this->BackLeftBuffer = this->FrontLeftBuffer = this->BackBuffer = this->FrontBuffer = 
     static_cast<unsigned int>(GL_COLOR_ATTACHMENT0); 

    // Save GL objects by static casting to standard C types. GL* types 
    // are not allowed in VTK header files. 
    QSize fboSize = fbo->size(); 
    this->Size[0] = fboSize.width(); 
    this->Size[1] = fboSize.height(); 
    this->NumberOfFrameBuffers = 1; 
    this->FrameBufferObject = static_cast<unsigned int>(fbo->handle()); 
    this->DepthRenderBufferObject = 0; // static_cast<unsigned int>(depthRenderBufferObject); 
    this->TextureObjects[0] = static_cast<unsigned int>(fbo->texture()); 
    this->OffScreenRendering = 1; 
    this->OffScreenUseFrameBuffer = 1; 
    this->Modified(); 
} 

void QVTKFrameBufferObjectItem::Start() 
{ 
    m_win->OpenGLInitState(); 
} 

void QVTKFrameBufferObjectItem::End() 
{ 
} 


void QVTKFrameBufferObjectItem::MakeCurrent() 
{ 
    this->window()->openglContext()->makeCurrent(this->window()); 
} 

void QVTKFrameBufferObjectItem::IsCurrent(vtkObject*, unsigned long, void*, void* call_data) 
{ 
    bool* ptr = reinterpret_cast<bool*>(call_data); 
    *ptr = this->window()->openglContext(); 
} 

void QVTKFrameBufferObjectItem::IsDirect(vtkObject*, unsigned long, void*, void* call_data) 
{ 
    int* ptr = reinterpret_cast<int*>(call_data); 
    *ptr = QGLFormat::fromSurfaceFormat(this->window()->openglContext()->format()).directRendering(); 
} 

void QVTKFrameBufferObjectItem::SupportsOpenGL(vtkObject*, unsigned long, void*, void* call_data) 
{ 
    int* ptr = reinterpret_cast<int*>(call_data); 
    *ptr = QGLFormat::hasOpenGL(); 
} 


QVTKFrameBufferObjectItem::QVTKFrameBufferObjectItem(QQuickItem *parent) : QQuickFramebufferObject(parent) 
{ 
    setAcceptedMouseButtons(Qt::AllButtons); 

    m_irenAdapter = new QVTKInteractorAdapter(this); 
    m_win = vtkSmartPointer<vtkInternalOpenGLRenderWindow>::New(); 

    // make a connection between the vtk signals and qt slots so that an initialized and madeCurrent opengl context is given to the vtk 
    // we probably need only the Start(), MakeCurrent() and End() one, but just to be sure... 
    mConnect = vtkSmartPointer<vtkEventQtSlotConnect>::New(); 
    mConnect->Connect(m_win, vtkCommand::WindowMakeCurrentEvent, this, SLOT(MakeCurrent())); 
    mConnect->Connect(m_win, vtkCommand::WindowIsCurrentEvent, this, SLOT(IsCurrent(vtkObject*, unsigned long, void*, void*))); 
    mConnect->Connect(m_win, vtkCommand::StartEvent, this, SLOT(Start())); 
    mConnect->Connect(m_win, vtkCommand::EndEvent, this, SLOT(End())); 
    mConnect->Connect(m_win, vtkCommand::WindowIsDirectEvent, this, SLOT(IsDirect(vtkObject*, unsigned long, void*, void*))); 
    mConnect->Connect(m_win, vtkCommand::WindowSupportsOpenGLEvent, this, SLOT(SupportsOpenGL(vtkObject*, unsigned long, void*, void*))); 
} 

QVTKFrameBufferObjectItem::~QVTKFrameBufferObjectItem() 
{ 
    mConnect->Disconnect(); // disconnect all slots 
    if (m_irenAdapter) 
     delete m_irenAdapter; 
} 

QQuickFramebufferObject::Renderer *QVTKFrameBufferObjectItem::createRenderer() const 
{ 
    return new QVTKFramebufferObjectRenderer(m_win); 
} 

vtkSmartPointer<vtkInternalOpenGLRenderWindow> QVTKFrameBufferObjectItem::GetRenderWindow() const 
{ 
    return m_win; 
} 

void QVTKFrameBufferObjectItem::init() 
{ 
} 

// theoretically not needed now - the Y is being flipped in render and devicePixelRatio will almost always be = 1 on a PC anyway...but lets keep it to be sure 
QMouseEvent QVTKFrameBufferObjectItem::openGLToNative(QMouseEvent const& event) 
{ 
    QPointF localPos(event.localPos()); 
    localPos.setX(localPos.x() * window()->devicePixelRatio()); 
    localPos.setY(localPos.y() * window()->devicePixelRatio()); 
    QMouseEvent nativeEvent(event.type(), localPos, event.button(), event.buttons(), event.modifiers()); 
    return nativeEvent; 
} 

void QVTKFrameBufferObjectItem::mouseMoveEvent(QMouseEvent * event) 
{ 
    m_win->GetInteractor()->SetSize(this->width(), this->height()); 
    QMouseEvent nativeEvent = openGLToNative(*event); 
    m_irenAdapter->ProcessEvent(&nativeEvent, this->m_win->GetInteractor()); 
} 

void QVTKFrameBufferObjectItem::mousePressEvent(QMouseEvent * event) 
{ 
    m_win->GetInteractor()->SetSize(this->width(), this->height()); 
    QMouseEvent nativeEvent = openGLToNative(*event); 
    m_irenAdapter->ProcessEvent(&nativeEvent, this->m_win->GetInteractor()); 
} 

void QVTKFrameBufferObjectItem::mouseReleaseEvent(QMouseEvent * event) 
{ 
    m_win->GetInteractor()->SetSize(this->width(), this->height()); 
    QMouseEvent nativeEvent = openGLToNative(*event); 
    m_irenAdapter->ProcessEvent(&nativeEvent, this->m_win->GetInteractor()); 
} 

void QVTKFrameBufferObjectItem::wheelEvent(QWheelEvent *event) 
{ 
    m_irenAdapter->ProcessEvent(event, this->m_win->GetInteractor()); 
} 


void QVTKFrameBufferObjectItem::keyPressEvent(QKeyEvent* event) 
{ 
    m_irenAdapter->ProcessEvent(event, this->m_win->GetInteractor()); 
} 

void QVTKFrameBufferObjectItem::keyReleaseEvent(QKeyEvent* event) 
{ 
    m_irenAdapter->ProcessEvent(event, this->m_win->GetInteractor()); 
} 
void QVTKFrameBufferObjectItem::focusInEvent(QFocusEvent * event) 
{ 
    m_irenAdapter->ProcessEvent(event, this->m_win->GetInteractor()); 
} 

void QVTKFrameBufferObjectItem::focusOutEvent(QFocusEvent * event) 
{ 
    m_irenAdapter->ProcessEvent(event, this->m_win->GetInteractor()); 
} 

es zu verwenden, um eine Instanz des Framebuffer in Ihrer qml Form definieren und über das Fenster strecken Sie in, zum Beispiel machen mögenwie folgt aus (Sie QVTKFrameBufferObjectItem als QVTKFrameBuffer registriert unter der Annahme, in qml zB wie diese qmlRegisterType<QVTKFrameBufferObjectItem>("VtkQuick", 1, 0, "QVTKFrameBuffer");):

import VtkQuick 1.0 
QVTKFrameBuffer 
{ 
    id: renderBuffer 
    anchors.fill : parent 
    Component.onCompleted : 
    { 
     myCppDisplay.framebuffer = renderBuffer // tell the c++ side of your app that this is the framebuffer into which it should render 
    } 
} 

Sie dann die vtkRenderWindow verwenden Sie myCppDisplay.framebuffer.GetRenderWindow() die gleiche Art und Weise erhalten Sie eine andere vtkRenderWindow verwenden würden, wenn Sie in ein Rendering wurden vtk-managed window, dh Sie können ihm vtkRenderer zuweisen, diesem Renderer Aktoren zuweisen, theWindow.Render() aufrufen, wie Sie es wünschen, und alles wird in die qml-Komponente gerendert, der Sie den Framebuffer zugewiesen haben.

Zwei Anmerkungen: 1) die vtk und qt verwenden unterschiedliche Koordinatensystem, müssen Sie die Y-Koordinate drehen ... Ich mache es durch Zuweisen einer Skalierung Transformation der Kamera, aber es gibt viele andere Möglichkeiten es tun:

vtkSmartPointer<vtkTransform> scale = vtkSmartPointer<vtkTransform>::New(); 
scale->Scale(1, -1, 1); 
renderer->GetActiveCamera()->SetUserTransform(scale); 

2) Dinge ziemlich schwierig zu bekommen, wenn Sie mehrere Threads beginnen - Sie müssen sicherstellen, dass Sie nicht in zwei verschiedenen Threads zu machen versuchen, weil sie für diese eine QtQuick Rendering-Thread konkurrieren . Dies bedeutet nicht, dass Sie renderWindow.Render() nicht parallel aufrufen - das ist einfach zu vermeiden - aber Sie müssen wissen, dass dieser qt-Thread auch für das Rendern der GUI verwendet wird, so dass Sie auf diese Weise in Schwierigkeiten geraten könnten VTK-Rendering).