|  | Home · All Classes · Main Classes · Grouped Classes · Modules · Functions |

# OpenGL for Embedded Systems Example

Files:

- qtopiacore/ahigl/qscreenahigl_qws.cpp
- qtopiacore/ahigl/qscreenahigl_qws.h
- qtopiacore/ahigl/qscreenahiglplugin.cpp
- qtopiacore/ahigl/qwindowsurface_ahigl.cpp
- qtopiacore/ahigl/qwindowsurface_ahigl_p.h

The OpenGL for Embedded Systems (ES) screen driver example shows how to use the Graphics Driver Framework in Qtopia Core to write an OpenGL ES integration. The example assumes basic knowledge of the graphics driver framework as discussed in the Accelerated Screen Driver Example.

In Qtopia Core, OpenGL ES can be used in different ways. It can be used to compose displays of overlapping top-level windows, where the top-most window is not opaque but allows features of the underlying windows to show through. Or it can be used to animate a transition from an existing window to a newly created one. A third use is to access hardware features of your device to accelerate widget painting, and a fourth use is simply to enable access to class QGLWidget. This example shows how to implement all of these.

The example uses an OpenGL ES implementation from ATI for the Imageon 2380. The OpenGL ES and EGL libraries are required for compiling and linking the example. If your target device is different, you must supply the corresponding libraries for that device, and you also might have to modify the example source code to match any API signature differences in your EGL library.

After compiling the example source, install the resulting screen driver plugin with the command `make install`. To start an application using the example's screen driver, either set the environment variable QWS_DISPLAY and then start the program, or start the program using the `-display` switch:

     myApplication -qws -display ahigl

To enable the window transition effect when showing new windows, run the application with `-display ahigl:effects`.

## QAhiGLScreen Class Definition

The example screen driver class is called QAhiGLScreen. "Ahi" stands for ATI Handheld Interface. The class can be used as the screen driver for other ATI handheld devices.

     class QAhiGLScreen : public QGLScreen
     {
     public:
         QAhiGLScreen(int displayId);
         virtual ~QAhiGLScreen();

         bool initDevice();
         bool connect(const QString &displaySpec);
         void disconnect();
         void shutdownDevice();

         void setMode(int width, int height, int depth);
         void blank(bool on);

         void exposeRegion(QRegion r, int changing);

         QWSWindowSurface* createSurface(QWidget *widget) const;
         QWSWindowSurface* createSurface(const QString &key) const;

         bool hasOpenGL();

     private:
         void updateTexture(int windowIndex);
         void redrawScreen();
         void drawWindow(QWSWindow *win, qreal progress);
         void drawQuad(const QRect &textureGeometry,
                       const QRect &subGeometry,
                       const QRect &screenGeometry);
         void drawQuadInverseY(const QRect &textureGeometry,
                               const QRect &subTexGeometry,
                               const QRect &screenGeometry);
         void drawQuadWavyFlag(const QRect &textureGeometry,
                               const QRect &subTexGeometry,
                               const QRect &screenGeometry,
                               float progress);

         QAhiGLScreenPrivate *d_ptr;
         friend class QAhiGLScreenPrivate;
     };

The screen driver class is derived from class QGLScreen, which is itself derived from class QScreen. If you will only use OpenGL ES for composing displays of top-level windows, you can create a screen driver class by deriving from QScreen directly. Otherwise your screen driver class must be derived from QGLScreen.

Note that class QGLScreen is not included in the Qt documentation. The design of QGLScreen is still preliminary and so it is not yet included in the published Qt API. However, class QScreen is included in the Qt documentation.

exposeRegion() is the function that does the composition and displaying. The private functions updateTexture(), redrawScreen(), and drawWindow() are helper functions used for composing displays. The private functions drawQuad(), drawQuadInverseY(), and drawQuadWavyFlag() are used for animating the showing of new windows.

     class ShowAnimation : public QTimeLine
     {
     public:
         ShowAnimation(QAhiGLScreenPrivate *screen);
         qreal valueForTime(int msec);
     };

The animated window transition effect is supported by the class ShowAnimation, which is derived from class QTimeLine and reimplements its valueForTime() function.

     struct WindowInfo
     {
         WindowInfo() : texture(0), animation(0) {}

         GLuint texture;
         QPointer<ShowAnimation> animation;
     };

     static QMap<QWSWindow*, WindowInfo*> windowMap;

Top-level windows that are not drawn using OpenGL are rendered the normal way into a shared memory segment. The driver supports this by creating a GL texture of the memory segment and drawing it to the screen. The `texture` identifier and a pointer to the active `animation` are held in an instance of `struct` `WindowInfo`. The instances of WindowInfo are held in a global map, `windowMap`.

     class QAhiGLScreenPrivate : public QObject
     {
         Q_OBJECT

     public:
         QAhiGLScreenPrivate(QAhiGLScreen *s);

     public slots:
         void windowEvent(QWSWindow *w, QWSServer::WindowEvent e);
         void redrawScreen();

     public:
         QAhiGLScreen *screen;

         EGLContext eglContext;
         EGLDisplay eglDisplay;
         EGLSurface eglSurface;

         QTimer updateTimer;
         bool doEffects;
     };

The QAhiGLScreenPrivate class keeps the necessary GL context variables used by QAhiGLScreen when compositioning the windows to the screen. In addition it subclasses QObject to enable use of the signal/slot mechanism. This is needed to be able to connect to the QWSServer::windowEvent() signal which will trigger the transition effect. QAhiGLScreenPrivate also contains a timer used for limiting the number of screen updates per seconds.

## QAhiGLScreen Class Implementation

The connect() function is the first function that is called after the constructor returns. This implementation just initializes the QScreen variables to some hardcoded values. A better implementation would query the hardware for it's current state.

At the end of the function we check if the driver has been started with the `effects` argument which should trigger use of transition effects.

     bool QAhiGLScreen::connect(const QString &displaySpec)
     {
         // Hardcoded values for this device
         w = 480;
         h = 640;
         dw = w;
         dh = h;
         d = 16;

         const int dpi = 120;
         physWidth = qRound(dw * 25.4 / dpi);
         physHeight = qRound(dh * 25.4 / dpi);

         if (displaySpec.section(':', 1, 1).contains("effects"))
             d_ptr->doEffects = true;

         return true;
     }

The initDevice() function is called on the server process and is responsible for initializing the hardware. This implementation use the EGL library to achieve this. Note that some of the data structures used in this API are specific to the current EGL implementation, as for instance the DummyScreen structure.

In this function we also connects to the QWSServer::windowEvent() signal if the transition effect is to be enabled.

     bool QAhiGLScreen::initDevice()
     {
         EGLint version, subversion;
         EGLint attrs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
                            EGL_STENCIL_SIZE, 8, EGL_DEPTH_SIZE, 16,
                            EGL_NONE };
         EGLint numConfig;
         EGLConfig eglConfig;

         d_ptr->eglDisplay = eglGetDisplay(0);
         if (d_ptr->eglDisplay == EGL_NO_DISPLAY) {
             qCritical("QAhiGLScreen::initDevice(): eglGetDisplay failed: 0x%x",
                       eglGetError());
             return false;
         }

         if (!eglInitialize(d_ptr->eglDisplay, &version, &subversion)) {
             qCritical("QAhiGLScreen::initDevice(): eglInitialize failed: 0x%x",
                       eglGetError());
             return false;
         }

         if (!eglChooseConfig(d_ptr->eglDisplay, attrs, &eglConfig, 1, &numConfig)) {
             qCritical("QAhiGLScreen::initDevice(): eglChooseConfig failed: 0x%x",
                       eglGetError());
             return false;
         }

         static DummyScreen win = { w, h };
         d_ptr->eglSurface = eglCreateWindowSurface(d_ptr->eglDisplay, eglConfig,
                                                    &win, 0);
         if (d_ptr->eglSurface == EGL_NO_SURFACE) {
             qCritical("QAhiGLScreen::initDevice(): eglCreateWindowSurface failed: 0x%x",
                       eglGetError());
             return false;
         }

         d_ptr->eglContext = eglCreateContext(d_ptr->eglDisplay, eglConfig,
                                              EGL_NO_CONTEXT, 0);
         if (d_ptr->eglContext == EGL_NO_CONTEXT) {
             qCritical("QAhiGLScreen::initDevice(): eglCreateContext failed: 0x%x",
                       eglGetError());
             return false;
         }

         if (!eglMakeCurrent(d_ptr->eglDisplay, d_ptr->eglSurface, d_ptr->eglSurface, d_ptr->eglContext)) {
             qCritical("QAhiGLScreen::initDevice(): eglMakeCurrent failed: 0x%x",
                       eglGetError());
             return false;
         }

         d_ptr->connect(QWSServer::instance(),
                        SIGNAL(windowEvent(QWSWindow*, QWSServer::WindowEvent)),
                        SLOT(windowEvent(QWSWindow*, QWSServer::WindowEvent)));
         QScreenCursor::initSoftwareCursor();

         return true;
     }

Before the application exists, the {QScreen::}{shutdownDevice()} is called in the server process to release the hardware resources. This implementation again use the EGL library to achieve this.

     void QAhiGLScreen::shutdownDevice()
     {
         eglMakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE,
                        EGL_NO_SURFACE, EGL_NO_CONTEXT);
         eglDestroyContext(d_ptr->eglDisplay, d_ptr->eglContext);
         eglDestroySurface(d_ptr->eglDisplay, d_ptr->eglSurface);
         eglTerminate(d_ptr->eglDisplay);
     }

Whenever an area on the screen needs to be updated the function exposeRegion() is called by the window system. This implementation doesn't actually redraw the screen, but starts a timer to redraw at a later time.

     void QAhiGLScreen::exposeRegion(QRegion r, int windowIndex)
     {
         if ((r & region()).isEmpty())
             return;

         updateTexture(windowIndex);

         if (!d_ptr->updateTimer.isActive())
             d_ptr->updateTimer.start(frameSpan);
     }

We won't go through the `updateTexture()` function but only mention that it uses the EGL functions `glTexImage2D()` and `glTexSubImage2D()` to copy the memory image of the widget into a OpenGL texture if necessary.

The actual window compositioning happens in the redrawScreen() function. It starts by using standard OpenGL to fill the screen with the background color.

     void QAhiGLScreen::redrawScreen()
     {
         glBindFramebufferOES(GL_FRAMEBUFFER_EXT, 0);
         glMatrixMode(GL_PROJECTION);
         glPushMatrix();
         glMatrixMode(GL_MODELVIEW);
         glPushMatrix();

         glMatrixMode(GL_PROJECTION);
         glLoadIdentity();
         glOrthof(0, w, h, 0, -999999, 999999);
         glViewport(0, 0, w, h);
         glMatrixMode(GL_MODELVIEW);
         glLoadIdentity();

         // Fill background color

         QColor bgColor = QWSServer::instance()->backgroundBrush().color();
         glClearColor(bgColor.redF(), bgColor.greenF(),
                      bgColor.blueF(), bgColor.alphaF());
         glClear(GL_COLOR_BUFFER_BIT);

Next it iterates over all the windows and use the helper function `drawWindow()` to draw each of them as a OpenGL texture to the screen. The texture id is either taken from the `WindowInfo` struct or from the window surface if it is a surface that's being painted using OpenGL, i.e a `QAhiGLWindowSurface`. We'll discuss this class shortly.

`drawWindow()` takes a `progress` parameter that is used to create an animation effect if it's less than 1.0. We won't discuss the `drawWindow()` function any further in this text, but point to the source code for more details.

     QWSWindowSurface* QAhiGLScreen::createSurface(const QString &key) const
     {
         if (key == QLatin1String("ahigl")) {
             return new QAhiGLWindowSurface(d_ptr->eglDisplay, d_ptr->eglSurface,
                                            d_ptr->eglContext);
         }

         return QScreen::createSurface(key);
     }

     QWSWindowSurface* QAhiGLScreen::createSurface(QWidget *widget) const
     {
         if (QApplication::type() == QApplication::GuiServer) {
             if (qobject_cast<QGLWidget*>(widget)) {
                 return new QAhiGLWindowSurface(widget, d_ptr->eglDisplay,
                                                d_ptr->eglSurface,
                                                d_ptr->eglContext);
             }

             const QRect rect = widget->frameGeometry();
             if (rect.width() <= 256 && rect.height() <= 256) {
                 return new QAhiGLWindowSurface(widget, d_ptr->eglDisplay,
                                                d_ptr->eglSurface,
                                                d_ptr->eglContext);
             }
         }

         return QScreen::createSurface(widget);
     }

The two createSurface() functions decides when instantiate a QAhiGLWindowSurface. We do this whenever we create a QGLWidget and also for top-level windows that are under a certain size. The size contraint is a necessary for this particular OpenGL ES implementation. Additionly this driver only supports use of OpenGL in the server process.

## QAhiGLWindowSurface Class Definition

To be able to support QGlWidget or use of OpenGL when using QPainter on a normal widget we need to create a subclass of QWSGLWindowSurface. In addition to reimplementing the standard functionality needed when subclassing a QWSWindowSurface, the QAhiGLWindowSurface also contains the `textureId()` function used by QAhiGLScreen.

     class QAhiGLWindowSurfacePrivate;

     class QAhiGLWindowSurface : public QWSGLWindowSurface
     {
     public:
         QAhiGLWindowSurface(QWidget *widget, EGLDisplay eglDisplay,
                             EGLSurface eglSurface, EGLContext eglContext);
         QAhiGLWindowSurface(EGLDisplay eglDisplay, EGLSurface eglSurface,
                             EGLContext eglContext);
         ~QAhiGLWindowSurface();

         QString key() const { return QLatin1String("ahigl"); }
         void setGeometry(const QRect &rect);
         QPaintDevice *paintDevice();
         void beginPaint(const QRegion &region);
         bool isValid() const;

         QByteArray permanentState() const;
         void setPermanentState(const QByteArray &);

         QImage image() const { return QImage(); }

         GLuint textureId() const;

     private:
         QAhiGLWindowSurfacePrivate *d_ptr;
     };

## QAhiGLWindowSurface Class Implementation

The implementation of OpenGL ES tested with this example only supports one OpenGL context. This context is therefore shared between QAhiGLScreen and all instances QAhiGLWindowSurface and passed in the constructor.

     QAhiGLWindowSurface::QAhiGLWindowSurface(QWidget *widget,
                                              EGLDisplay eglDisplay,
                                              EGLSurface eglSurface,
                                              EGLContext eglContext)
         : QWSGLWindowSurface(widget)
     {
         d_ptr = new QAhiGLWindowSurfacePrivate(eglDisplay, eglSurface, eglContext);
         d_ptr->device = new QWSGLPaintDevice(widget);

         setSurfaceFlags(QWSWindowSurface::Buffered);
     }

The constructor also creates an instance of `QWSGLPaintDevice` which is returned in the paintDevice() function. This ensures that all QPainters used on this surface will use an OpenGL enabled QPaintEngine.

This example makes use of the OpenGL FrameBufferObject extension. All painting is performed into a framebuffer object which is later compositioned onto the screen by QAhiGLScreen. Allocateding the framebuffer object is done in setGeometry().

     void QAhiGLWindowSurface::setGeometry(const QRect &rect)
     {
         QSize size = rect.size();

         const QWidget *w = window();
         if (w && !w->mask().isEmpty()) {
             const QRegion region = w->mask()
                                    & rect.translated(-w->geometry().topLeft());
             size = region.boundingRect().size();
         }

         if (geometry().size() != size) {

             // Driver specific limitations:
             //   FBO maximimum size of 256x256
             //   Depth buffer required

             d_ptr->textureWidth = qMin(256, nextPowerOfTwo(size.width()));
             d_ptr->textureHeight = qMin(256, nextPowerOfTwo(size.height()));

             glGenTextures(1, &d_ptr->texture);
             glBindTexture(GL_TEXTURE_2D, d_ptr->texture);

             glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
             glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
             glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
             glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

             const int bufSize = d_ptr->textureWidth * d_ptr->textureHeight * 2;
             GLshort buf[bufSize];
             memset(buf, 0, sizeof(GLshort) * bufSize);

             glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, d_ptr->textureWidth,
                          d_ptr->textureHeight, 0,
                          GL_RGBA, GL_UNSIGNED_BYTE, buf);

             glGenRenderbuffersOES(1, &d_ptr->depthbuf);
             glBindRenderbufferOES(GL_RENDERBUFFER_EXT, d_ptr->depthbuf);
             glRenderbufferStorageOES(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT16,
                                      d_ptr->textureWidth, d_ptr->textureHeight);

             glGenFramebuffersOES(1, &d_ptr->frameBufferObject);
             glBindFramebufferOES(GL_FRAMEBUFFER_EXT, d_ptr->frameBufferObject);

             glFramebufferTexture2DOES(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                                       GL_TEXTURE_2D, d_ptr->texture, 0);
             glFramebufferRenderbufferOES(GL_FRAMEBUFFER_EXT,
                                          GL_DEPTH_ATTACHMENT_EXT,
                                          GL_RENDERBUFFER_EXT, d_ptr->depthbuf);
             glBindFramebufferOES(GL_FRAMEBUFFER_EXT, 0);
         }

         QWSGLWindowSurface::setGeometry(rect);
     }

Since there can be several instances of the QAhiGLWindowSurface, we need to make sure that the correct framebuffer object is active before painting. This is done by reimplementing QWindowSurface::beginPaint():

     void QAhiGLWindowSurface::beginPaint(const QRegion &region)
     {
         QWSGLWindowSurface::beginPaint(region);

         if (d_ptr->frameBufferObject)
             glBindFramebufferOES(GL_FRAMEBUFFER_EXT, d_ptr->frameBufferObject);
     }

Finally we need to make sure that whenever a widget grows beyond the size supported by this driver (256 x 256), the surface is deleted and a new standard surface is created instead. This is handled by reimplementing QWSWindowSurface::isValid():

     bool QAhiGLWindowSurface::isValid() const
     {
         if (!qobject_cast<QGLWidget*>(window())) {
             const QRect r = window()->frameGeometry();
             if (r.width() > 256 || r.height() > 256)
                 return false;
         }
         return true;
     }