Qt 4.3: OpenGL for Embedded Systems Example

|  | 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

## Introduction

This example demonstrates how you can use OpenGL for Embedded Systems (ES) in your own screen driver and add your graphics driver to Qtopia Core. In Qtopia Core, painting is done in software, normally performed in two steps: First, each client renders its windows onto its window surface in memory using a paint engine. Then the server uses the screen driver to compose the window surface images and copy the composition to the screen. (See the Qtopia Core Architecture documentation for details.)

This example is not for the novice. It assumes the reader is familiar with both OpenGL and the screen driver framework demonstrated in the Accelerated Graphics Driver Example.

An OpenGL screen driver for Qtopia Core can use OpenGL ES in three ways. First, the Qtopia Core server can use the driver to compose multiple window images and then show the composition on the screen. Second, clients can use the driver to accelerate OpenGL painting operations using the QOpenGLPaintEngine class. Finally, clients can use the driver to do OpenGL operations with instances of the QGLWidget class. This example implements all three cases.

The example uses an implementation of OpenGL ES from ATI for the Imageon 2380. The OpenGL include files gl.h and egl.h must be installed to compile the example, and the OpenGL and EGL libraries must be installed for linking. If your target device is different, you must install the include files and libraries for that device, and you also might need to modify the example source code, if any API signatures in your EGL library differ from the ones used here.

After compiling and linking the example source, install the screen driver plugin with the command `make install`. To start an application that uses the plugin, you can either set the environment variable QWS_DISPLAY and then start the application, or just start the application with the `-display` switch, as follows:

     myApplication -qws -display ahigl

The example driver also implements an animated transition effect for use when showing new windows or reshowing windows that have been minimized. To enable this transition effect, run the application with `-display ahigl:effects`.

## The Class Definitions

The example comprises three main classes plus some helper classes. The three main classes are the plugin (QAhiGLScreenPlugin), which is defined in qscreenahiglplugin.cpp, the screen driver (QAhiGLScreen), which is defined in qscreenahigl_qws.h, and the window surface (QAhiGLWindowSurface), which is defined in qwindowsurface_ahigl_p.h. The "Ahi" prefix in these class names stands for ATI Handheld Interface. The example was written for the ATI Imageon 2380, but it can also be used as a template for other ATI handheld devices.

### The Plugin Class Definition

The screen driver plugin is class QAhiGLScreenPlugin.

     class QAhiGLScreenPlugin : public QScreenDriverPlugin
     {
     public:
         QAhiGLScreenPlugin();

         QStringList keys() const;
         QScreen *create(const QString&, int displayId);
     };

QAhiGLScreenPlugin is derived from class QScreenDriverPlugin, which in turn is derived from QObject.

### The Screen Driver Class Definitions

The screen driver classes are the public class QAhiGLScreen and its private implementation class QAhiGLScreenPrivate. QAhiGLScreen is derived from QGLScreen, which is derived from QScreen. If your screen driver will only do window compositions and display them, then you can derive your screen driver class directly from QScreen. But if your screen driver will do accelerated graphics rendering operations with the QOpenGLPaintEngine, or if it will handle instances of class QGLWidget, then you must derive your screen driver class from QGLScreen.

     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 invalidateTexture(int windowIndex);
         void redrawScreen();
         void drawWindow(QWSWindow *win, qreal progress);
         void drawQuad(const QRect &textureGeometry,
                       const QRect &subGeometry,
                       const QRect &screenGeometry);
         void drawQuadWavyFlag(const QRect &textureGeometry,
                               const QRect &subTexGeometry,
                               const QRect &screenGeometry,
                               float progress);

         QAhiGLScreenPrivate *d_ptr;
         friend class QAhiGLScreenPrivate;
     };

All functions in the public API of class QAhiGLScreen are virtual functions declared in its base classes. hasOpenGL() is declared in QGLScreen. It simply returns true indicating our example screen driver does support OpenGL operations. The other functions in the public API are declared in QScreen. They are called by the Qtopia Core server at the appropriate times.

Note that class QScreen is a documented class but class QGLScreen is not. This is because the design of class QGLScreen is not yet final.

The only data member in class QAhiGLScreen is a standard d_ptr, which points to an instance of the driver's private implementation class QAhiGLScreenPrivate. The driver's internal state is stored in the private class. Using the so-called d-pointer pattern allows you to make changes to the driver's internal design without breaking binary compatibility.

     class QAhiGLScreenPrivate : public QObject
     {
         Q_OBJECT

     public:
         QAhiGLScreenPrivate(QAhiGLScreen *s);

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

     public:
         QAhiGLScreen *screen;
         QAhiGLCursor *cursor;

         EGLContext eglContext;
         EGLDisplay eglDisplay;
         EGLSurface eglSurface;

         QTimer updateTimer;
         bool doEffects;
     };

Class QAhiGLScreenPrivate is derived from QObject so that it can use the Qt signal/slot mechanism. QAhiGLScreen is not a QObject, so it can't use the signal/slot mechanism. Signals meant for our screen driver are received by slots in the private implementation class, in this case, windowEvent() and redrawScreen().

### The Window Surface Class Definitions

The window surface classes are QAhiGLWindowSurface and its private implementation class QAhiGLWindowSurfacePrivate. We create class QAhiGLWindowSurface so the screen driver can use the OpenGL paint engine and the OpenGL widget, classes QOpenGLPaintEngine and QGLWidget. QAhiGLWindowSurface is derived from the more general OpenGL window surface class, QWSGLWindowSurface, which is derived from QWSWindowSurface.

     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;
     };

In addition to implementing the standard functionality required by any new subclass of QWSWindowSurface, QAhiGLWindowSurface also contains the textureId() function used by QAhiGLScreen.

The same d-pointer pattern is used in this window surface class. The private implementation class is QAhiGLWindowSurfacePrivate. It allows making changes to the state variables of the window surface without breaking binary compatibility.

     class QAhiGLWindowSurfacePrivate
     {
     public:
         QAhiGLWindowSurfacePrivate(EGLDisplay eglDisplay,
                                    EGLSurface eglSurface,
                                    EGLContext eglContext);

         QPaintDevice *device;

         int textureWidth;
         int textureHeight;

         GLuint texture;
         GLuint frameBufferObject;
         GLuint depthbuf;

         EGLDisplay display;
         EGLSurface surface;
         EGLContext context;
     };

In this case, our private implementation class has no member functions except for its constructor. It contains only public data members which hold state information for the window surface.

### The Helper Classes

The example screen driver maintains a static map of all the windows it is showing on the screen. Each window is mapped to an instance of struct WindowInfo.

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

         GLuint texture;
         QPointer<ShowAnimation> animation;
     };

     static QMap<QWSWindow*, WindowInfo*> windowMap;

As each new window is created, an instance of struct WindowInfo is allocated and inserted into the window map. WindowInfo uses a GLuint to identify the OpenGL texture it creates for the window. Note that the example driver, in addition to drawing windows using OpenGL, also supports drawing windows in the normal way without OpenGL, but it uses an OpenGL texture for the rendering operations in either case. Top-level windows that are drawn without OpenGL are first rendered in the normal way into a shared memory segment, which is then converted to a OpenGL texture and drawn to the screen.

To animate the window transition effect, WindowInfo uses an instance of the helper class ShowAnimation. The animation is created by the windowEvent() slot in QAhiGLScreenPrivate, whenever a Show window event is emitted by the window server. The server emits this signal when a window is shown the first time and again later, when the window is reshown after having been minimized.

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

Class ShowAnimation is derived from the QTimeLine class, which is used for controlling animations. QTimeLine is a QObject, so ShowAnimation can use the Qt signal/slot mechanism. We will see how the timeline's valueChanged() and finished() signals are used to control the animation and then destroy the instance of ShowAnimation, when the animation ends. The ShowAnimation constructor needs the pointer to the screen driver's private implementation class so it can set up these signal/slot connections.

## The Class Implementations

### The Plugin Class Implementation

QAhiGLScreenPlugin is a straightforward derivation of QScreenDriverPlugin. It reimplements keys() and create(). They are called as needed by the Qtopia Core server. Recall that the server detects that the ahigl screen driver has been requested, either by including "ahigl" in the value for the environment variable QWS_DISPLAY, or by running your application with a command line like the following.

     myApplication -qws -display ahigl

The server calls keys(), which returns a QStringList containing the singleton "ahigl" matching the requested screen driver and telling the server that it can use our example screen driver. The server then calls create(), which creates the instance of QAhiGLScreen.

     Q_EXPORT_PLUGIN2(qahiglscreen, QAhiGLScreenPlugin)

In the code snippet above, the macro Q_EXPORT_PLUGIN2 is used to export the plugin class, QAhiGLScreen, for the qahiglscreen plugin. Further information regarding plugins and how to create them can be found at How to Create Qt Plugins.

### The Screen Driver Class Implementations

The plugin creates the singleton instance of QAhiGLScreen. The constructor is passed a `displayId`, which is used in the base class QGLScreen to identify the server that the screen driver is connected to. The constructor also creates its instance of QAhiGLScreenPrivate, which instantiates a QTimer. The timeout() signal of this timer is connected to the redrawScreen() slot so the timer can be used to limit the frequency of actual drawing operations in the hardware.

The public API of class QAhiGLScreen consists of implementations of virtual functions declared in its base classes. The function hasOpenGL() is declared in base class QGLScreen. The others are declared in base class QScreen.

The connect() function is the first one called by the server after the screen driver is constructed. It initializes the QScreen data members to hardcoded values that describe the ATI screen. A better implementation would query the hardware for the corresponding values in it's current state and use those. It asks whether the screen driver was started with the `effects` option and sets the `doEffects` flag accordingly.

     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 by the server after connect(). It uses EGL library functions to initialize the ATI hardware. Note that some data structures used in this example are specific to the EGL implementation used, e.g., the DummyScreen structure.

     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)));

         d_ptr->cursor = new QAhiGLCursor;
         qt_screencursor = d_ptr->cursor;

         return true;
     }

Note the signal/slot connection at the bottom of initDevice(). We connect the server's QWSServer::windowEvent() signal to the windowEvent() slot in the screen driver's private implementation class. The windowEvent() slot handles three window events, QWSServer::Create, QWSServer::Destroy, and QWSServer::Show.

     void QAhiGLScreenPrivate::windowEvent(QWSWindow *window,
                                           QWSServer::WindowEvent event)
     {
         switch (event) {
         case QWSServer::Create:
             windowMap[window] = new WindowInfo;
             break;
         case QWSServer::Show:
             if (doEffects)
                 windowMap[window]->animation = new ShowAnimation(this);
             break;
         case QWSServer::Destroy:
             delete windowMap[window];
             windowMap.remove(window);
             break;
         default:
             break;
         }
     }

The function manages instances of the helper classes associated with each window. When a QWSServer::Create event occurs, it means a new top-level window has been created. In this case, an instance of helper class WindowInfo is created and inserted into the window map with the pointer to the new window as its key. When a QWSServer::Destroy event occurs, a window is being destroyed, and its mapping is removed from the window map. These two events are straightforward. The tricky bits happen when a QWSServer::Show event occurs. This case occurs when a window is shown for the first time and when it is reshown after having been minimized. If the window transition effect has been enabled, a new instance of the helper class ShowAnimation is created and stored in a QPointer in the window's instance of WindowInfo. The constructor of ShowAnimation automatically starts the animation of the transition effect.

     ShowAnimation::ShowAnimation(QAhiGLScreenPrivate *screen)
         : QTimeLine(animationLength)
     {
         setUpdateInterval(frameSpan);
         connect(this, SIGNAL(valueChanged(qreal)), screen, SLOT(redrawScreen()));
         connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
         start();
     }

To ensure that a ShowAnimation is not deleted until its animation ends, the finished() signal is connected to the deleteLater() slot. When the animation ends, the finished() signal is emitted and the deleteLater() slot deletes the ShowAnimation. The key here is that the pointer to the ShowAnimation is stored in a QPointer in the WindowInfo class. This QPointer will also be notified when the ShowAnimation is deleted, so the QPointer in WindowInfo can null itself out, if and only if it is still pointing to the instance of ShowAnimation being deleted.

The valueForTime() function in QTimeLine is reimplemented in ShowAnimation to return time values that represent a curved path for the window transition effect.

     qreal ShowAnimation::valueForTime(int msec)
     {
         const qreal t = msec / qreal(duration());
         return 3*t*t - 2*t*t*t;
     }

valueForTime() is called internally, when the time interval it computed during the previous call has elapsed. If it computes a next time value that is different from the one computed previously, the valueChanged() signal is emitted. The ShowAnimation constructor shown above connects this signal to the redrawScreen() slot in the screen driver's private implementation class. This is how the animation actually happens.

The screen driver's implementation of exposeRegion() is where the main work of the screen driver is meant to be done, i.e., updating the screen. It is called by the window system to update a particular window's region of the screen. But note that it doesn't actually update the screen, i.e., it doesn't actually call redrawScreen() directly, but starts the updateTimer, which causes redrawScreen() to be called once for each updateTimer interval. This means that all calls to exposeRegion() during an updateTimer interval are handled by a single call to redrawScreen(). Thus updateTimer can be used to limit the frequency of screen updates.

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

         invalidateTexture(windowIndex);

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

The call to the private function invalidateTexture() destroys the window's existing texture (image). This ensures that a new texture will be created for the window, when redrawScreen() is eventually called.

But there is a caveat to using updateTimer to limit the frequency of screen updates. When the driver's animated transition effect for new windows is enabled and a new window is being shown for the first time or reshown after having been minimized, an instance of ShowAnimation is created to run the animation. The valueChanged() signal of this ShowAnimation is also connected to the redrawScreen() slot, and QTimeLine, the base class of our ShowAnimation, uses its own, internal timer to limit the speed of the animation. This means that in the driver as currently written, if the window transition effect is enabled (i.e. if the plugin is started, with `-display ahigl:effects`), then redrawScreen() can be called both when the update timer times out and when the ShowAnimation timer times out, so the screen might get updated more often than the frequency established by the update timer. This may or may not be a bug, depending on your own hardware, if you use this example as a template for your own OpenGL driver.

The screen driver's private function redrawScreen() constructs the window compositions. It is called only by the function of the same name in the screen driver's private implementation class.

     void QAhiGLScreenPrivate::redrawScreen()
     {
         updateTimer.stop();
         screen->redrawScreen();
     }

Recall that this redrawScreen() in the private implementation class is a slot function connected to two signals, the `timeout()` signal of the updateTimer in the private implementation class, and the valueChanged() signal of the helper class ShowAnimation. Thus, the screen is only ever updated when a timeout of one of the two timers occurs. This is important for two reasons. First, the screen is meant to be updated no more than once per updateTimer interval. Second, however, if the animated window transition effect is requested, the screen might be updated more often than that, and this might be a bug if the hardware can't handle more frequent updates.

The redrawScreen() in QAhiGLScreen begins 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 the list of all client windows obtained from the server, extracting from each window its instance of QWSWIndowSurface, then using that window surface to create an OpenGL texture, and finally calling the helper function drawWindow() to draw the texture on the screen.

         QList<QWSWindow*> windows = QWSServer::instance()->clientWindows();
         for (int i = windows.size() - 1; i >= 0; --i) {
             QWSWindow *win = windows.at(i);
             QWSWindowSurface *surface = win->windowSurface();
             if (!surface)
                 continue;

             WindowInfo *info = windowMap[win];

             if (!info->texture) {
                 if (surface->key() == QLatin1String("ahigl"))
                     info->texture = static_cast<QAhiGLWindowSurface*>(surface)->textureId();
                 else
                     info->texture = createTexture(surface->image());
             }
             qreal progress;
             if (info->animation)
                 progress = info->animation->currentValue();
             else
                 progress = 1.0;

             glBindTexture(GL_TEXTURE_2D, info->texture);
             drawWindow(win, progress);
         } // for i

Note the call to glBindTexture() immediately before the call to drawWindow(). This call binds the identifer `GL_TEXTURE_2D` to the texture we have just created. This makes our texture accessible to functions in the OpenGL libraries. If you miss that point, digging into the internals of drawWindow() won't make much sense.

Finally, the cursor is added to the window composition, and in the last statement, the whole thing is displayed on the screen.

         // Draw cursor

         const QAhiGLCursor *cursor = d_ptr->cursor;
         if (cursor->texture) {
             glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
             glBindTexture(GL_TEXTURE_2D, d_ptr->cursor->texture);
             drawQuad(cursor->boundingRect(), cursor->boundingRect(),
                      cursor->boundingRect());
         }

         glPopMatrix();
         glMatrixMode(GL_PROJECTION);
         glPopMatrix();
         glMatrixMode(GL_MODELVIEW);

         eglSwapBuffers(d_ptr->eglDisplay, d_ptr->eglSurface);
     }

The call to `drawWindow(win,progress)`, in addition to passing a pointer to the window to be redrawn, also passes the `progress` parameter obtained by calling QTimeLine::currentValue() on the window's instance of ShowAnimation. Recall that the current value of the timeline is updated internally by a timer local to the timeline, and the redrawScreen() slot is called whenever the current value changes. The progress value will only be used if the animated transition effect has been enabled. These extra calls of redrawScreen() may cause the screen to be updated more often than the rate determined by updateTimer. This must be taken into account, if you set your updateTimer to timeout at the maximum screen update frequency your hardware can handle.

The drawWindow() function is not shown here and not explained further, but the call to drawWindow() is the entry point to a hierarchy of private helper functions that execute sequences of OpenGL and EGL library calls. The reader is assumed to be familiar enough with the OpenGL and EGL APIs to understand the code in these helper functions on his own. Besides drawWindow(), the list of these helper functions includes drawQuad(), drawQuadWavyFlag(), the two overloadings of drawQuad_helper() (used by drawQuad() and drawQuadWacyFlag()), and setRectCoords().

Note the two different ways the window's texture can be created in redrawScreen(). If the window surface is an OpenGL window surface (QAhiGLWindowSurface described below), the texture is obtained from the window surface directly by calling its textureId() function. But when the window surface is not an OpenGL one, the static function createTexture() is called with the window surface's image to copy that image into an OpenGL texture. This is done with the EGL functions glTexImage2D() and glTexSubImage2D(). createTexture() is another function that should be understandable for exsperienced OpenGL users, so it is not shown or explained further here.

The two implementations of createSurface() are for instantiating new window surfaces. The overloading with the widget parameter is called in the client.

     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);
     }

If the parameter is an OpenGL widget, or, when it isn't an OpenGL widget but its size is no bigger than 256 x 256, we instantiate our subclass QAhiGLWindowSurface. Otherwise, we instantiate a QWSWindowSurface. The size contraint is a limitation of the OpenGL ES libraries we are using for our ATI device.

Note the test at the top of the function asking if our application process is the server. We only create instances of QAhiGLWindowSurface if our client is in the server process. This is because of an implementation restriction required by the OpenGL library used in the example. They only support use of OpenGL in the server process. Hence a client can use the QAhiGLWindowSurface if the client is in the server process.

The other overloading of createSurface() is called by the server to create a window surface that will hold a copy of a client side window surface.

     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);
     }

This overloading accepts a QString parameter identifying the type of window surface to instantiate. QAhiGLWindowSurface is instantiated if the parameter is `ahigl`. Otherwise, a normal QWSWindowSurface is instantiated. The client's window surface communicates its image data to the server's window surface through shared memory.

The implementation of setMode(), is a stub in this example. It would normally reset the frame buffer's resolution. Its parameters are the width, height, and the bit depth for the frame buffer's new resolution. If you implement setMode() in your screen driver, remember that it must emit a signal to warn other applications to redraw their frame buffers with the new resolution. There is no significance to setMode() not being implemented in this example. It simply wasn't implemented. However, the stub had to be included because QScreen declares setMode() to be pure virtual.

Before the application exits, the server will call shutdownDevice() to release the hardware resources. This is also done using EGL library functions.

     void QAhiGLScreen::shutdownDevice()
     {
         delete d_ptr->cursor;
         d_ptr->cursor = 0;
         qt_screencursor = 0;

         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);
     }

The server will also call disconnect(), but this function is only a stub in this example.

### The window Surface Class Implementations

QAhiGLScreen creates instances of QAhiGLWindowSurface in its two createSurface() functions, and there are two constructors for QAhiGLWindowSurface that correspond to these two versions of createSurface(). The constructor accepting a widget parameter is called by the client side version of createSurface(), and the constructor without the widget parameter is called by the server side version. There will be a window surface constructed on the server side for each one constructed on the client side.

     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);
     }

     QAhiGLWindowSurface::QAhiGLWindowSurface(EGLDisplay eglDisplay,
                                              EGLSurface eglSurface,
                                              EGLContext eglContext)
     {
         d_ptr = new QAhiGLWindowSurfacePrivate(eglDisplay, eglSurface, eglContext);
         setSurfaceFlags(QWSWindowSurface::Buffered);
     }

The constructors create an instance of QAhiGLWindowSurfacePrivate, the private implementation class, which contains all the state variables for QAhiGLWindowSurface. The client side constructor also creates an instance of QWSGLPaintDevice, the OpenGL paint device, for return by paintDevice(). This ensures that all QPainters used on this surface will use an OpenGL enabled QPaintEngine. It is a bit of jiggery pokery, which is required because paintDevice() is declared pure virtual. Normally, the client side constructor will be called with an OpenGL widget, which has its own paintEngine() function that returns the global static OpenGL paint engine, but because the constructor also accepts a normal widget, it must be able to find the OpenGL paint engine in that case as well, so since paintDevice() must be implemented anyway, the constructor creates an instance of QWSGLPaintDevice, which can always return the global static pointer to QOpenGLPaintEngine.

The OpenGL library implementation used for this example only supports one OpenGL context. This context is therefore shared among the single instance of QAhiGLScreen and all instances of QAhiGLWindowSurface. It is passed to both constructors.

This example uses the OpenGL frame buffer object extension, which allows for accelerating OpenGL painting operations. Using this OpenGL extension, painting operations are performed in a frame buffer object, which QAhiGLScreen later uses to construct window compositions on the screen. Allocation of the frame buffer object is performed in setGeometry(). A safer way to use this extension would be to first test to see if the extension is supported by your OpenGL library, and use a different approach if it is not.

     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;
     }