Skip to content
Snippets Groups Projects
Commit 4c77c460 authored by Utkarsh Ayachit's avatar Utkarsh Ayachit
Browse files

QVTKOpenGLWidget: better paintGL/Render interaction.

`QVTKOpenGLWidget::paintGL` no longer results in a `Render` call on
every paintGL call. It only does that for cases where it's an absolute
must e.g. paintGL after recreating FBO. QVTKOpenGLWidget now relies on
VTK applications calling `Render` on the window when data or rendering
has changed.

For those few times where QVTKOpenGLWidget has to call Render in
`paintGL`, it does that via a new virtual method `renderVTK`.

This also removes deferred-rendering API. That API was only added to
attempt an API match with QVTKWidget. Since that is already not true, it
makes sense to keep this new class as simple as possible. Besides, the
new `renderVTK` API makes it possible for subclasses to implement
deferred-rendering support, if needed.
parent f5512f22
No related branches found
No related tags found
No related merge requests found
......@@ -123,11 +123,10 @@ protected:
//-----------------------------------------------------------------------------
QVTKOpenGLWidget::QVTKOpenGLWidget(QWidget* parentWdg, Qt::WindowFlags f)
: Superclass(parentWdg, f)
, DeferRenderInPaintEvent(false)
, InteractorAdaptor(NULL)
, FBO(nullptr)
, InPaintGL(false)
, SkipRenderInPaintGL(false)
, DoVTKRenderInPaintGL(false)
, Logger(nullptr)
{
this->Observer->SetTarget(this);
......@@ -135,15 +134,13 @@ QVTKOpenGLWidget::QVTKOpenGLWidget(QWidget* parentWdg, Qt::WindowFlags f)
// default to strong focus
this->setFocusPolicy(Qt::StrongFocus);
this->setUpdateBehavior(QOpenGLWidget::PartialUpdate);
this->InteractorAdaptor = new QVTKInteractorAdapter(this);
this->InteractorAdaptor->SetDevicePixelRatio(this->devicePixelRatio());
this->setMouseTracking(true);
this->DeferedRenderTimer.setSingleShot(true);
this->DeferedRenderTimer.setInterval(0);
this->connect(&this->DeferedRenderTimer, SIGNAL(timeout()), SLOT(doDeferredRender()));
// QOpenGLWidget::resized() is triggered when the default FBO in QOpenGLWidget is recreated.
// We use the same signal to recreate our FBO.
this->connect(this, SIGNAL(resized()), SLOT(recreateFBO()));
......@@ -251,12 +248,6 @@ QVTKInteractor* QVTKOpenGLWidget::GetInteractor()
return QVTKInteractor::SafeDownCast(this->GetRenderWindow()->GetInteractor());
}
//-----------------------------------------------------------------------------
void QVTKOpenGLWidget::setDeferRenderInPaintEvent(bool val)
{
this->DeferRenderInPaintEvent = val;
}
//-----------------------------------------------------------------------------
void QVTKOpenGLWidget::copyFromFormat(const QSurfaceFormat& format, vtkRenderWindow* win)
{
......@@ -343,6 +334,10 @@ void QVTKOpenGLWidget::recreateFBO()
// end up rendering fully transparent windows (see through background)
// unless we fill it with alpha=1.0 (See paraview/paraview#17159).
vtkSetBackgroundAlpha(this->RenderWindow, 1.0);
// Since the context or frame buffer was recreated, if a paintGL call ensues,
// we need to ensure we're requesting VTK to render.
this->DoVTKRenderInPaintGL = true;
}
//-----------------------------------------------------------------------------
......@@ -393,7 +388,6 @@ void QVTKOpenGLWidget::resizeGL(int w, int h)
{
this->RenderWindow->SetSize(w * this->devicePixelRatio(),
h * this->devicePixelRatio());
this->SkipRenderInPaintGL = false;
}
this->Superclass::resizeGL(w, h);
}
......@@ -409,29 +403,22 @@ void QVTKOpenGLWidget::paintGL()
Q_ASSERT(this->FBO);
Q_ASSERT(this->FBO->handle() == this->RenderWindow->GetDefaultFrameBufferId());
QScopedValueRollback<bool> var(this->InPaintGL, true);
this->Superclass::paintGL();
if (this->SkipRenderInPaintGL)
if (this->DoVTKRenderInPaintGL && !this->renderVTK())
{
vtkQVTKOpenGLWidgetDebugMacro("paintGL:skipped");
this->SkipRenderInPaintGL = false;
}
else
{
if (this->DeferRenderInPaintEvent)
{
vtkQVTKOpenGLWidgetDebugMacro("paintGL:defer render");
this->deferRender();
}
else
{
vtkQVTKOpenGLWidgetDebugMacro("paintGL:render");
this->doDeferredRender();
}
vtkQVTKOpenGLWidgetDebugMacro("paintGL:skipped-renderVTK");
// This should be very rare, but it's conceivable that subclasses of
// QVTKOpenGLWidget are simply not ready to do a
// render on VTK render window when widget is being painted.
// Leave the buffer unchanged.
return;
}
// We just did a render, if we needed it. Turn the flag off.
this->DoVTKRenderInPaintGL = false;
// If render was triggered by above calls, that may change the current context
// due to things like progress events triggering updates on other widgets
// (e.g. progress bar). Hence we need to make sure to call makeCurrent()
......@@ -490,10 +477,6 @@ void QVTKOpenGLWidget::windowFrameEventCallback()
Q_ASSERT(this->RenderWindow);
vtkQVTKOpenGLWidgetDebugMacro("frame");
// Render happened. If we have requested a render to happen, it has happened,
// so no need to request another render. Stop the timer.
this->DeferedRenderTimer.stop();
if (!this->InPaintGL)
{
// Handing vtkOpenGLRenderWindow::Frame is tricky. VTK code traditionally
......@@ -516,33 +499,47 @@ void QVTKOpenGLWidget::windowFrameEventCallback()
vtkQVTKOpenGLWidgetDebugMacro("update");
this->update();
this->SkipRenderInPaintGL = true;
this->DoVTKRenderInPaintGL = false;
}
else
{
this->SkipRenderInPaintGL = false;
}
vtkQVTKOpenGLWidgetDebugMacro("buffer bad -- do not show");
// Since this->FBO right now is garbage, if paint event is received before
// a Render request is made on the render window, we will have to Render
// explicitly.
this->DoVTKRenderInPaintGL = true;
}
}
}
//-----------------------------------------------------------------------------
void QVTKOpenGLWidget::deferRender()
bool QVTKOpenGLWidget::renderVTK()
{
this->DeferedRenderTimer.start();
}
vtkQVTKOpenGLWidgetDebugMacro("renderVTK");
Q_ASSERT(this->FBO);
// Bind the FBO we'll be rendering into. This may not be needed, since VTK will
// bind it anyways, but we'll be extra cautious.
this->FBO->bind();
//-----------------------------------------------------------------------------
void QVTKOpenGLWidget::doDeferredRender()
{
vtkRenderWindowInteractor* iren = this->RenderWindow ? this->RenderWindow->GetInteractor() : NULL;
if (iren && this->FBO)
if (iren)
{
// Bind the FBO we'll be rendering into (is this needed? VTK will bind it anyways).
this->FBO->bind();
iren->Render();
this->DeferedRenderTimer.stop(); // not necessary, but no harm.
}
else if (this->RenderWindow)
{
this->RenderWindow->Render();
}
else
{
// no render window set, just fill with white.
QOpenGLFunctions* f = QOpenGLContext::currentContext()->functions();
f->glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
f->glClear(GL_COLOR_BUFFER_BIT);
}
return true;
}
//-----------------------------------------------------------------------------
......
......@@ -57,35 +57,51 @@
*
* @endcode
*
* @section OpenGL Context
* @section OpenGLContext OpenGL Context
*
* In QOpenGLWidget (superclass for QVTKOpenGLWidget), all rendering happens in a
* framebuffer object. Thus, care must be taken in the rendering code to never
* directly re-bind the framebuffer with ID 0.
*
* @section Handling Render and Paint.
*
* In VTK, rendering is requested calling `vtkRenderWindow::Render`, while with
* Qt, a widget is updated in an **paint** event. Since QVTKOpenGLWidget renders
* to a framebuffer, any OpenGL calls don't directly update the image shown on the
* screen. Instead Qt composes the rendered image with the widget stack and displays
* on screen. This poses a challenge when VTK (or the VTK application) triggers a
* render outside the paint event by calling `vtkRenderWindow::Render`, since the rendering
* results won't show on screen until Qt composes the rendered image. To handle that,
* QVTKOpenGLWidget listens to the vtkCommand::WindowFrameEvent fired by
* vtkGenericOpenGLRenderWindow when vtkRenderWindow::Frame is called. That
* typically happens at the end of a render to swap the front and back buffers
* (for example). If QVTKOpenGLWidget receives that event outside of a paintGL
* call, then it requests Qt to update the widget by calling `QWidget::update`.
* Note, when `paintGL` subsequently gets called, QVTKOpenGLWidget leaves the
* framebuffer untouched i.e. doesn't call vtkRenderWindow::Render again.
* If vtkRenderWindow::SwapBuffers if off, then the `QWidget::update` is not
* called. The rationale is that the changes were done in back buffer, most
* likely and hence are not intended to be shown onscreen.
* directly re-bind the default framebuffer i.e. ID 0.
*
* QVTKOpenGLWidget creates an internal QOpenGLFramebufferObject, independent of the
* one created by superclass, for vtkRenderWindow to do the rendering in. This
* explicit double-buffering is useful in avoiding temporary back-buffer only
* renders done in VTK (e.g. when making selections) from destroying the results
* composed on screen.
*
* @section RenderAndPaint Handling Render and Paint.
*
* QWidget subclasses (including `QOpenGLWidget` and `QVTKOpenGLWidget`) display
* their contents on the screen in `QWidget::paint` in response to a paint event.
* `QOpenGLWidget` subclasses are expected to do OpenGL rendering in
* `QOpenGLWidget::paintGL`. QWidget can receive paint events for various
* reasons including widget getting focus/losing focus, some other widget on
* the UI e.g. QProgressBar in status bar updating, etc.
*
* In VTK applications, any time the vtkRenderWindow needs to be updated to
* render a new result, one call `vtkRenderWindow::Render` on it.
* vtkRenderWindowInteractor set on the render window ensures that as
* interactions happen that affect the rendered result, it calls `Render` on the
* render window.
*
* Since paint in Qt can be called more often then needed, we avoid potentially
* expensive `vtkRenderWindow::Render` calls each time that happens. Instead,
* QVTKOpenGLWidget relies on the VTK application calling
* `vtkRenderWindow::Render` on the render window when it needs to update the
* rendering. `paintGL` simply passes on the result rendered by the most render
* vtkRenderWindow::Render to Qt windowing system for composing on-screen.
*
* There may still be occasions when we may have to render in `paint` for
* example if the window was resized or Qt had to recreate the OpenGL context.
* In those cases, `QVTKOpenGLWidget::paintGL` can request a render by calling
* `QVTKOpenGLWidget::renderVTK`.
*
* @section Caveats
* QVTKOpenGLWidget only supports **OpenGL2** rendering backend. Thus is not available
* if VTK is built with **VTK_RENDERING_BACKEND** set to **OpenGL**.
*
* QVTKOpenGLWidget is targeted for Qt version 5.5 and above.
*
*/
#ifndef QVTKOpenGLWidget_h
#define QVTKOpenGLWidget_h
......@@ -96,7 +112,6 @@
#include "vtkGUISupportQtModule.h" // for export macro
#include "vtkNew.h" // needed for vtkNew
#include "vtkSmartPointer.h" // needed for vtkSmartPointer
#include <QTimer> // needed for QTimer.
class QOpenGLDebugLogger;
class QOpenGLFramebufferObject;
......@@ -108,8 +123,6 @@ class vtkGenericOpenGLRenderWindow;
class VTKGUISUPPORTQT_EXPORT QVTKOpenGLWidget : public QOpenGLWidget
{
Q_OBJECT
Q_PROPERTY(
bool deferRenderInPaintEvent READ deferRenderInPaintEvent WRITE setDeferRenderInPaintEvent)
typedef QOpenGLWidget Superclass;
public:
QVTKOpenGLWidget(QWidget* parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags());
......@@ -129,22 +142,6 @@ public:
*/
virtual QVTKInteractor* GetInteractor();
//@{
/**
* When set to true (default is false), paintEvent() will never directly trigger
* a render on the vtkRenderWindow (via vtkRenderWindowInteractor::Render()).
* Instead, it starts a timer that then triggers the render on idle. This, in
* general is a good strategy for cases where Render may take a while with
* applications wanting to report progress and consequently trigger paint
* events on other widgets like progress bars, etc.
* There is one caveat: when paintEvent() is called using a redirected paint device,
* then this flag is ignored and the paintEvent() will trigger
* vtkRenderWindowInteractor::Render(), if needed.
*/
virtual void setDeferRenderInPaintEvent(bool val);
virtual bool deferRenderInPaintEvent() const { return this->DeferRenderInPaintEvent; }
//@}
/**
* Sets up vtkRenderWindow ivars using QSurfaceFormat.
*/
......@@ -169,19 +166,6 @@ signals:
void mouseEvent(QMouseEvent* event);
protected slots:
/**
* Request to defer a render call i.e. start the mDeferedRenderTimer. When the
* timer times out, it will call doDeferredRender() to do the actual rendering.
*/
virtual void deferRender();
/**
* Called when the mDeferedRenderTimer times out to do the rendering.
*/
virtual void doDeferredRender();
bool event(QEvent* evt) Q_DECL_OVERRIDE;
/**
* Called as a response to `QOpenGLContext::aboutToBeDestroyed`. This may be
* called anytime during the widget lifecycle. We need to release any OpenGL
......@@ -202,6 +186,7 @@ private slots:
void startEventCallback();
protected:
bool event(QEvent* evt) Q_DECL_OVERRIDE;
void initializeGL() Q_DECL_OVERRIDE;
void resizeGL(int w, int h) Q_DECL_OVERRIDE;
void paintGL() Q_DECL_OVERRIDE;
......@@ -220,9 +205,29 @@ protected:
*/
void requireRenderWindowInitialization();
protected:
bool DeferRenderInPaintEvent;
/**
* This method may be called in `paintGL` to request VTK to do a render i.e.
* trigger render on the render window via its interactor.
*
* It will return true if render (or an equivalent action) was performed to
* update the frame buffer made available to VTK for rendering with latest
* rendering.
*
* Default implementation never returns false. However, subclasses can return
* false to indicate to QVTKOpenGLWidget that it cannot generate a reasonable
* image to be displayed in QVTKOpenGLWidget. In which case, the `paintGL`
* call will return leaving the `defaultFramebufferObject` untouched.
*
* Since by default `QOpenGLWidget::UpdateBehavior` is set to
* QOpenGLWidget::PartialUpdate, this means whatever was rendered in the frame
* buffer in most recent successful call will be preserved, unless the widget
* was forced to recreate the FBO as a result of resize or screen change.
*
* @sa Section @ref RenderAndPaint.
*/
virtual bool renderVTK();
protected:
vtkSmartPointer<vtkGenericOpenGLRenderWindow> RenderWindow;
QVTKInteractorAdapter* InteractorAdaptor;
......@@ -240,10 +245,9 @@ private:
*/
void windowFrameEventCallback();
QTimer DeferedRenderTimer;
QOpenGLFramebufferObject* FBO;
bool InPaintGL;
bool SkipRenderInPaintGL;
bool DoVTKRenderInPaintGL;
vtkNew<QVTKOpenGLWidgetObserver> Observer;
friend class QVTKOpenGLWidgetObserver;
QOpenGLDebugLogger* Logger;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment