samedi 1 décembre 2012

Custom OpenGL context with Qt (Linux/Windows)

Context

From Qt 4.8 it is possible to create an OpenGL (>=3.0) core context [1], but one can't yet create a debug context. Even if it seems that next major version of Qt will permit that [2], it is interesting to know how to use QGLWidget without a Qt OpenGL context. Actually custom context will always allow more freedom.

Requirements

I'll use c++11 in code samples.
Given a GLContext class and a function to create an instance of this class on Linux or Windows OS :
namespace virtrev { namespace glc {

class GLContext
{
 struct Config;
 struct WMInformation;
 struct ContextPreconfigurationOut;
 struct WindowInformation;

 virtual ~GLContext();

 virtual void configure( Config const & config, WMInformation const & wm_information, ContextPreconfigurationOut & context_preconfiguration_out ) = 0;
 virtual void create( WindowInformation const & window_information, GLContext * shared ) = 0;
 virtual void bind() = 0;
 virtual void unbind() = 0;
 virtual void swap() = 0;
};

GLContext * create_gl_context();
We will be able to replace the default QGLContext used in QGLwidget.

Config : OpenGL portable characteristic.
WMInformation : platform specific window manager information.
ContextPreconfigurationOut : platform specific information (configure output) to adapt QGLWidget before creating the context.
WindowInformation : platform specific windows (QGLWidget internal windows) information.

Let's start

We have to create a QGLContext child, so we can replace the QGLWidget OpenGL context.

QGLContextAdapter.hpp
#ifndef QGLCONTEXTADAPTER_HPP
#define QGLCONTEXTADAPTER_HPP

#include 
#include 

namespace vglc = virtrev::glc;

/** Must be allocated on the heap! Because widget will take ownershipo of this. */
class QGLContextAdapter : public QGLContext
{
public:
 explicit QGLContextAdapter( vglc::GLContext::Config const & config );
 ~QGLContextAdapter();

 bool chooseContext( QGLContext const * shareContext = 0 ) override;
 void makeCurrent() override;
 void doneCurrent() override;
 void swapBuffers() const override;

 void setDevice(QPaintDevice *device) { QGLContext::setDevice(device); }

private:
 void SetupQGLFormatFromConfig( vglc::GLContext::Config const & config, QGLFormat & format );

 std::unique_ptr gl_context;
};

#endif // QGLCONTEXTADAPTER_HPP
QGLContextAdapter.cpp
#include "QGLContextAdapter.hpp"

#if defined(Q_WS_X11)
 #include 
 #include 
 #include 

 void SetupQGLFormatFromVisual( Display * display, XVisualInfo * visual, QGLFormat & format );
#endif

QGLContextAdapter::QGLContextAdapter( vglc::GLContext::Config const & config )
: QGLContext( QGLFormat() )
{
 gl_context.reset( vglc::create_gl_context() );

 vglc::GLContext::WMInformation wm_info;
 vglc::GLContext::ContextPreconfigurationOut context_preconf;

#if defined(Q_WS_X11)
 XVisualInfo * visual;
 wm_info.display = QX11Info::display();
 wm_info.screen_idx = 0;
 context_preconf.visual = &visual; // we'll get the visual needed by the context
#endif

 gl_context->configure( config, wm_info, context_preconf );
 QGLFormat new_format;

#if defined(Q_WS_X11)
 //Under linux the QGLFormat can be incompatible. So set it from the visual used to configure the context.
 SetupQGLFormatFromVisual( QX11Info::display(), visual, new_format );
#else
 //Perhaps under windows we should do the same, but it seems useless. So set the QGLFormat directly from config.
 SetupQGLFormatFromConfig( config, new_format );
#endif

 setFormat( new_format );
}

QGLContextAdapter::~QGLContextAdapter()
{
 setValid( false );
}

void QGLContextAdapter::SetupQGLFormatFromConfig( vglc::GLContext::Config const & config, QGLFormat & format )
{
 format.setAccum( false );
 format.setAccumBufferSize( 0 );

 format.setAlpha( config.alpha_size > 0 );
 format.setAlphaBufferSize( config.alpha_size);

 format.setBlueBufferSize( config.blue_size );
 format.setGreenBufferSize( config.green_size );
 format.setRedBufferSize( config.red_size );

 format.setRgba( true );

 format.setDepth( config.depth_size > 0 );
 format.setDepthBufferSize( config.depth_size );

 format.setDirectRendering( true );

 format.setDoubleBuffer( config.double_buffer );

 format.setSampleBuffers( config.sample_count > 1 );
 format.setSamples( config.sample_count );

 format.setStencil( config.stencil_size > 0 );
 format.setStencilBufferSize( config.stencil_size );

 format.setStereo( config.stereo );

 format.setOverlay( false );
 format.setPlane( 0 );
 format.setProfile( QGLFormat::CoreProfile );
 format.setSwapInterval( 0 );
 format.setVersion( 1, 0 );
}

bool QGLContextAdapter::chooseContext( QGLContext const * shared )
{
 QPaintDevice * dev = device();
 QGLWidget * widget = dynamic_cast(dev);

 vglc::GLContext::WindowInformation win_info;

#if defined(Q_WS_X11)
 Window window = widget->winId();
 win_info.window_handle = &( window );
#elif defined(Q_WS_WIN)
 HWND window_handle = widget->winId();
 win_info.window_handle = &window_handle;
#endif

 vglc::GLContext * shared_ctx = NULL;

 if( shared != NULL )
 {
  QGLContextAdapter const * context_adapter = dynamic_cast(shared);
  if( context_adapter != NULL )
  {
   shared_ctx = context_adapter->gl_context.get();
  }
 }

 gl_context->create( win_info, shared_ctx );

 setWindowCreated( true );

 return true;
}

void QGLContextAdapter::makeCurrent()
{
 gl_context->bind();
}

void QGLContextAdapter::doneCurrent()
{
 gl_context->unbind();
}

void QGLContextAdapter::swapBuffers() const
{
 gl_context->swap();
}

#if defined(Q_WS_X11)

struct GetVisualAttrib
{
 GetVisualAttrib( Display * display, XVisualInfo * visual )
 : display(display), visual(visual)
 {
 }

 int operator () ( int attrib )
 {
  int value;
  glXGetConfig( display, visual, attrib, &value );
  return value;
 }

 Display * display;
 XVisualInfo * visual;
};

void SetupQGLFormatFromVisual( Display * display, XVisualInfo * visual, QGLFormat & format )
{
 GetVisualAttrib getter( display, visual );

 int const accum_red_size = getter( GLX_ACCUM_RED_SIZE );
 int const accum_green_size = getter( GLX_ACCUM_GREEN_SIZE );
 int const accum_blue_size = getter( GLX_ACCUM_BLUE_SIZE );
 int const accum_alpha_size = getter( GLX_ACCUM_ALPHA_SIZE );
 int const accum_size = accum_red_size + accum_green_size + accum_blue_size + accum_alpha_size;

 format.setAccum( accum_size > 0 );
 format.setAccumBufferSize( accum_size / 4 );

 int const alpha_size = getter(GLX_ALPHA_SIZE);
 format.setAlpha( alpha_size > 0 );
 format.setAlphaBufferSize( alpha_size );

 format.setBlueBufferSize( getter(GLX_BLUE_SIZE) );
 format.setGreenBufferSize( getter(GLX_GREEN_SIZE) );
 format.setRedBufferSize( getter(GLX_RED_SIZE) );

 format.setRgba( getter(GLX_RGBA) );

 int const depth_size = getter( GLX_DEPTH_SIZE );
 format.setDepth( depth_size > 0 );
 format.setDepthBufferSize( depth_size );

 format.setDirectRendering( true );

 format.setDoubleBuffer( getter(GLX_DOUBLEBUFFER) );

 format.setSampleBuffers( false );
 format.setSamples(0);

 int const stencil_size = getter(GLX_STENCIL_SIZE);
 format.setStencil( stencil_size > 0 );
 format.setStencilBufferSize( stencil_size );

 format.setStereo( getter(GLX_STEREO) );

 format.setOverlay( false );
 format.setPlane( 0 );
 format.setProfile( QGLFormat::CoreProfile );
 format.setSwapInterval( 0 );
 format.setVersion( 1, 0 );
}

#endif // defined(Q_WS_X11)

And now the only thing to do is to use this custom context in the WGLWidget :
vglc::GLContext::Config config; //setup elsewhere

QGLContextAdapter * create_context()
{
 return new QGLContextAdapter( config );
}

GLWidget::GLWidget( QWidget * parent )
:   QGLWidget(
#if defined(Q_WS_WIN)
// under windows it's not possible to set context later
   create_context(),
#endif
   parent
 )
{
#if defined(Q_WS_X11)
// under linux if we pass the context in initialization list, QGLWidget will not use the good format
 QGLContextAdapter * context = create_context();
 setFormat( context->format() );
 context->setDevice( this );
 setContext( context );
#endif
}

References

1. qt-project.org
2. qt-project.org

Aucun commentaire:

Enregistrer un commentaire