Skip to content
Snippets Groups Projects
Commit d7fa7e55 authored by Utkarsh Ayachit's avatar Utkarsh Ayachit Committed by Kitware Robot
Browse files

Merge topic 'qvtkopenglwidget_fixes'


4c77c460 QVTKOpenGLWidget: better paintGL/Render interaction.

Acked-by: default avatarKitware Robot <kwrobot@kitware.com>
Reviewed-by: Cory Quammen's avatarCory Quammen <cory.quammen@kitware.com>
Merge-request: !2823
parents f3fc48c5 4c77c460
No related branches found
No related tags found
No related merge requests found
......@@ -123,13 +123,12 @@ protected:
//-----------------------------------------------------------------------------
QVTKOpenGLWidget::QVTKOpenGLWidget(QWidget* parentWdg, Qt::WindowFlags f)
: Superclass(parentWdg, f)
, DeferRenderInPaintEvent(false)
, InteractorAdaptor(NULL)
, EnableHiDPI(false)
, OriginalDPI(0)
, FBO(nullptr)
, InPaintGL(false)
, SkipRenderInPaintGL(false)
, DoVTKRenderInPaintGL(false)
, Logger(nullptr)
{
this->Observer->SetTarget(this);
......@@ -137,15 +136,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()));
......@@ -256,12 +253,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)
{
......@@ -372,6 +363,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;
}
//-----------------------------------------------------------------------------
......@@ -422,7 +417,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);
}
......@@ -438,29 +432,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()
......@@ -519,10 +506,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
......@@ -545,33 +528,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.
* directly re-bind the default framebuffer i.e. ID 0.
*
* @section Handling Render and Paint.
* 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.
*
* 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.
* @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.
*/
......@@ -174,19 +171,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
......@@ -207,6 +191,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;
......@@ -225,9 +210,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;
......@@ -248,10 +253,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