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

# Accelerated Screen Driver Example

Files:

- qtopiacore/svgalib/svgalibpaintdevice.cpp
- qtopiacore/svgalib/svgalibpaintdevice.h
- qtopiacore/svgalib/svgalibpaintengine.cpp
- qtopiacore/svgalib/svgalibpaintengine.h
- qtopiacore/svgalib/svgalibplugin.cpp
- qtopiacore/svgalib/svgalibscreen.cpp
- qtopiacore/svgalib/svgalibscreen.h
- qtopiacore/svgalib/svgalibsurface.cpp
- qtopiacore/svgalib/svgalibsurface.h

The accelerated screen driver example shows how to write an accelerated screen driver for Qtopia Core.

Painting in Qtopia Core is a pure software implementation and is normally performed in two steps: Each window is rendered onto a QWSWindowSurface using QPaintEngine, and then the server composes the surface images and copies them to the screen. Qtopia Core uses QRasterPaintEngine (a raster-based implementation of QPaintEngine) to implement painting operations and QScreen to implement window composition.

Starting with Qtopia Core 4.2, it is possible to add an accelerated graphics driver to take advantage of available hardware resources, using the following approach:

1. Create a Custom Screen
2. Implement a Custom Raster Paint Engine
3. Make the Widgets Aware of Your Paint Engine

Instead of interfacing the graphics hardware directly, this example relies on SVGAlib being installed on your system. SVGAlib is a small graphics library which provides acceleration for many common graphics card used on desktop computers. It should work on most workstations and has a small and simple API.

The driver implementation in this example can be used as a template when implementing your own driver. After compiling the example you should install the screen driver plugin with the command `make install`. To start an application using the example driver, you can either set the environment variable `QWS_DISPLAY` or start the program using the `-display` switch:

     myApplication -qws -display svgalib

Note that in order to keep the example as simple as possible, our driver only works with screen modes having 32 bits per pixel and does not work with multiple processes.

## Step 1: Create a Custom Screen

The custom screen is created by deriving from the QScreen class:

     ** This file is part of the example classes of the Qt Toolkit.
     **
     ** This file may be used under the terms of the GNU General Public
     ** License version 2.0 as published by the Free Software Foundation
     ** and appearing in the file LICENSE.GPL included in the packaging of
     ** this file.  Please review the following information to ensure GNU
     ** General Public Licensing requirements will be met:
     ** http://www.trolltech.com/products/qt/opensource.html
     **
     ** If you are unsure which license is appropriate for your use, please
     ** review the following information:
     ** http://www.trolltech.com/products/qt/licensing.html or contact the
     ** sales department at sales@trolltech.com.
     **
     ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
     ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
     **
     ****************************************************************************/

     #ifndef SVGALIBSCREEN_H
     #define SVGALIBSCREEN_H

     #include <QScreen>

     #include <vga.h>
     #include <vgagl.h>

     class SvgalibScreen : public QScreen
     {
     public:
         SvgalibScreen(int displayId) : QScreen(displayId) {}
         ~SvgalibScreen() {}

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

         void setMode(int, int, int) {}
         void blank(bool) {}

         void exposeRegion(QRegion r, int changing);
         void blit(const QImage &img, const QPoint &topLeft, const QRegion &region);
         void solidFill(const QColor &color, const QRegion &region);

     private:
         GraphicsContext *context;
     };

The connect(), disconnect(), initDevice() and shutdownDevice() functions are declared as pure virtual functions in QScreen and must be implemented. The mentioned functions are used to configure the hardware, or query its configuration: The connect() and disconnect() functions are used by both the server and client processes. The connect() function must initialize all member variables so that the driver is in a consistent state. The initDevice() and shutdownDevice() is only used by the server process and must leave the hardware in a state consistent with the driver configuration.

The setMode() and blank() are also declared as pure virtual functions in QScreen, but our driver does nothing in these functions. The last three functions are the ones involved in putting pixels on the screen and is where we will do the actual acceleration.

The `context` variable is a pointer to a SVGAlib specific type. Note that this example will not try to explain the details of using the SVGAlib library.

### SvgalibScreen Class Implementation

The connect() function is the first function that is called after the constructor. It queries SVGAlib about the graphics mode and initializes the variables.

     bool SvgalibScreen::connect(const QString &displaySpec)
     {
         Q_UNUSED(displaySpec);

         int mode = vga_getdefaultmode();
         if (mode <= 0) {
             qCritical("SvgalibScreen::connect(): invalid vga mode");
             return false;
         }

         vga_modeinfo *modeinfo = vga_getmodeinfo(mode);

         QScreen::lstep = modeinfo->linewidth;
         QScreen::dw = QScreen::w = modeinfo->width;
         QScreen::dh = QScreen::h = modeinfo->height;
         QScreen::d = modeinfo->bytesperpixel * 8;
         QScreen::size = QScreen::lstep * dh;
         QScreen::data = 0;

         const int dpi = 72;
         QScreen::physWidth = qRound(QScreen::dw * 25.4 / dpi);
         QScreen::physHeight = qRound(QScreen::dh * 25.4 / dpi);

         return true;
     }

A screen driver must initialize the following variables inherited from QScreen: `data`, `lstep`, `w`, `h`, `dw`, `dh`, `d`, `physWidth` and `physHeight`. In this example we do not have any information of the real physical size of the screen, so we set these values with the assumption of a screen with 72 DPI.

     bool SvgalibScreen::initDevice()
     {
         if (vga_init() != 0) {
             qCritical("SvgalibScreen::initDevice(): unable to initialize svgalib");
             return false;
         }

         int mode = vga_getdefaultmode();
         if (vga_setmode(mode) == -1) {
             qCritical("SvgalibScreen::initialize(): unable to set graphics mode");
             return false;
         }

         if (gl_setcontextvga(mode) != 0) {
             qCritical("SvgalibScreen::initDevice(): unable to set vga context");
             return false;
         }
         context = gl_allocatecontext();
         gl_getcontext(context);

         vga_modeinfo *modeinfo = vga_getmodeinfo(mode);
         if (!(modeinfo->flags & IS_LINEAR)) {
             qCritical("SvgalibScreen::initDevice(): graphics memory not linear");
             return false;
         }
         QScreen::data = vga_getgraphmem();

         QScreenCursor::initSoftwareCursor();
         return true;
     }

The initDevice() function is called after connect(), but only on the server process. This function should do the necessary hardware initialization. We have chosen to use the software cursor in this example. If you want to use a hardware cursor, you should create a subclass of QScreenCursor, create an instance of it, and make the global variable `qt_screencursor` point to this instance.

     void SvgalibScreen::shutdownDevice()
     {
         gl_freecontext(context);
         vga_setmode(TEXT);
     }

The shutdownDevice() function is only used on the server process and is called before the disconnect() function. It does whatever hardware releated cleanup that is necessary (the disconnect() function in this example does nothing).

Note that you at this point will have a fully working screen driver provided that the `QScreen::data` variable is initialized to point to a valid linear framebuffer. The rest of this example will show where to take advantage of the accelerated capabilities available on the hardware.

     void SvgalibScreen::exposeRegion(QRegion region, int changing)
     {
         QScreen::exposeRegion(region, changing);
         // flip between buffers if implementing a double buffered driver
     }

The next function we implement is exposeRegion(). The default implementation will do the necessary composing of the top-level windows and call solidFill() and blit() whenever it is required. We do not want to change this behavior in this driver so we just call the default implementation. However, if you are implementing a double buffered screen driver, this is where you would like to flip between the buffers.

Finally, we accelerate the final copying of pixels to the screen by reimplementing the solidFill() and blit() functions:

     void SvgalibScreen::solidFill(const QColor &color, const QRegion &reg)
     {
         if (depth() != 32 || depth() != 16) {
             QScreen::solidFill(color, reg);
             return;
         }

         const QVector<QRect> rects = (reg & region()).rects();
         for (int i = 0; i < rects.size(); ++i) {
             const QRect r = rects.at(i);
             gl_fillbox(r.left(), r.top(), r.width(), r.height(), color.rgba());
         }
     }

     void SvgalibScreen::blit(const QImage &img, const QPoint &topLeft,
                              const QRegion &reg)
     {
         bool do_fallback = true;

         if (depth() == 16 && img.format() == QImage::Format_RGB16)
             do_fallback = false;
         if (depth() == 32 && img.format() == QImage::Format_ARGB32_Premultiplied)
             do_fallback = false;

         if (do_fallback) {
             QScreen::blit(img, topLeft, reg);
             return;
         }

         const QVector<QRect> rects = (reg & region()).rects();

         for (int i = 0; i < rects.size(); ++i) {
             const QRect r = rects.at(i);
             gl_putboxpart(r.x(), r.y(), r.width(), r.height(),
                           img.width(), img.height(),
                           static_cast<void*>(const_cast<uchar*>(img.bits())),
                           r.x() - topLeft.x(), r.y() - topLeft.y());
         }
     }

## Step 2: Implement a Custom Raster Paint Engine

The acceleration of the painting operations, on the other hand, is done by subclassing the QRasterPaintEngine class. Subclassing QRasterPaintEngine is a powerful mechanism for accelerating graphic primitives while getting software fallbacks for all the primitives you do not accelerate. This example will only accelerate one of the drawRects() functions:

     #include <private/qpaintengine_raster_p.h>

     class SvgalibPaintEngine : public QRasterPaintEngine
     {
     public:
         SvgalibPaintEngine();
         ~SvgalibPaintEngine();

         bool begin(QPaintDevice *device);
         bool end();
         void updateState(const QPaintEngineState &state);
         void drawRects(const QRect *rects, int rectCount);

     private:
         void setClip(const QRegion &region);
         void updateClip();

         QPen pen;
         bool simplePen;
         QBrush brush;
         bool simpleBrush;
         QMatrix matrix;
         bool simpleMatrix;
         QRegion clip;
         bool clipEnabled;
         bool simpleClip;
         bool opaque;
         bool aliased;
         bool sourceOver;
         QPaintDevice *device;
     };

Only non-rotated, aliased and opaque rectangles will be accelerated. We store this state in the private member variables. The private functions `setClip()` and `updateClip()` are helper functions used when updating the engine's state.

Private Header Files
Note the include statement used by this class. The files prefixed with `private/` are so-called private headers file within Qtopia Core. Private header files are not part of a Qtopia Core installation and are only present while compiling Qtopia Core. To be able to compile using private header files you need to use a `qmake` binary within a compiled Qtopia Core package. Also note that private header files may change without notice between releases.

### SvgalibPaintEngine Class Implementation

The `begin()` function should initialize the internal state of the paint engine, and must also call QRasterPaintEngine::begin() to initialize the state stored in QRasterPaintEngine:

     bool SvgalibPaintEngine::begin(QPaintDevice *dev)
     {
         device = dev;
         pen = Qt::NoPen;
         simplePen = true;
         brush = Qt::NoBrush;
         simpleBrush = true;
         matrix = QMatrix();
         simpleMatrix = true;
         setClip(QRect(0, 0, device->width(), device->height()));
         simpleClip = true;
         opaque = true;
         aliased = true;
         sourceOver = true;

         return QRasterPaintEngine::begin(dev);
     }

The implementation of `end()` removes the clipping constraints that might have been set in SVGAlib:

     bool SvgalibPaintEngine::end()
     {
         gl_setclippingwindow(0, 0, device->width() - 1, device->height() - 1);
         return QRasterPaintEngine::end();
     }

The QRasterPaintEngine::end() function must be called to allow QRasterPaintEngine to clean up it's internal state.

     void SvgalibPaintEngine::updateState(const QPaintEngineState &state)
     {
         QPaintEngine::DirtyFlags flags = state.state();

         if (flags & DirtyTransform) {
             matrix = state.matrix();
             simpleMatrix = (matrix.m12() == 0 && matrix.m21() == 0);
         }

         if (flags & DirtyPen) {
             pen = state.pen();
             simplePen = (pen.width() == 0 || pen.widthF() <= 1)
                         && (pen.style() == Qt::NoPen || pen.style() == Qt::SolidLine)
                         && (pen.color().alpha() == 255);
         }

         if (flags & DirtyBrush) {
             brush = state.brush();
             simpleBrush = (brush.style() == Qt::SolidPattern
                            || brush.style() == Qt::NoBrush)
                           && (brush.color().alpha() == 255);
         }

         if (flags & DirtyClipRegion)
             setClip(state.clipRegion());

         if (flags & DirtyClipEnabled) {
             clipEnabled = state.isClipEnabled();
             updateClip();
         }

         if (flags & DirtyClipPath) {
             setClip(QRegion());
             simpleClip = false;
         }

         if (flags & DirtyCompositionMode) {
             const QPainter::CompositionMode m = state.compositionMode();
             sourceOver = (m == QPainter::CompositionMode_SourceOver);
         }

         if (flags & DirtyOpacity)
             opaque = (state.opacity() == 256);

         if (flags & DirtyHints)
             aliased = !(state.renderHints() & QPainter::Antialiasing);

         QRasterPaintEngine::updateState(state);
     }

The updateState() function updates the paint engine's internal state. We accept its matrix if it doesn't do any shearing, and save the matrix for future usage. The pen is accepted if it is opaque and only one pixel wide. The rest of the engine's properties are updated following the same pattern. Again it is important that the QRasterPaintEngine::updateState() function is called.

     void SvgalibPaintEngine::setClip(const QRegion &region)
     {
         if (region.isEmpty())
             clip = QRect(0, 0, device->width(), device->height());
         else
             clip = matrix.map(region) & QRect(0, 0, device->width(), device->height());
         clipEnabled = true;
         updateClip();
     }

The `setClip()` helper function enables clipping to the given region. Note that an empty region means that clipping is disabled.

     void SvgalibPaintEngine::updateClip()
     {
         QRegion clipRegion = QRect(0, 0, device->width(), device->height());

         if (!systemClip().isEmpty())
             clipRegion &= systemClip();
         if (clipEnabled)
             clipRegion &= clip;

         simpleClip = (clipRegion.rects().size() <= 1);

         const QRect r = clipRegion.boundingRect();
         gl_setclippingwindow(r.left(), r.top(),
                              r.x() + r.width(),
                              r.y() + r.height());
     }

The `updateClip()` function checks if the clip is "simple",i.e., that it can be represented by only one rectangle, and updates the clip region in SVGAlib.

     void SvgalibPaintEngine::drawRects(const QRect *rects, int rectCount)
     {
         const bool canAccelerate = simplePen && simpleBrush && simpleMatrix
                                    && simpleClip && opaque && aliased
                                    && sourceOver;
         if (!canAccelerate) {
             QRasterPaintEngine::drawRects(rects, rectCount);
             return;
         }

         for (int i = 0; i < rectCount; ++i) {
             const QRect r = matrix.mapRect(rects[i]);
             if (brush != Qt::NoBrush) {
                 gl_fillbox(r.left(), r.top(), r.width(), r.height(),
                            brush.color().rgba());
             }
             if (pen != Qt::NoPen) {
                 const int c = pen.color().rgba();
                 gl_hline(r.left(), r.top(), r.right(), c);
                 gl_hline(r.left(), r.bottom(), r.right(), c);
                 gl_line(r.left(), r.top(), r.left(), r.bottom(), c);
                 gl_line(r.right(), r.top(), r.right(), r.bottom(), c);
             }
         }
     }

Finally, the actual acceleration is implemented in the drawRects() function. The QRasterPaintEngine fallback is used whenever the rectangle is not simple enough.

## Step 3: Make the Widgets Aware of Your Paint Engine

To activate your paint engine, two new classes are needed: The `SvgalibPaintDevice` which derives from QCustomRasterPaintDevice and the `SvgalibSurface` class which derives from QWSWindowSurface.

### The SvgalibPaintDevice Class

First you must create a subclass of the QCustomRasterPaintDevice class and reimplement its paintEngine() and memory() functions:

     class SvgalibPaintDevice : public QCustomRasterPaintDevice
     {
     public:
         SvgalibPaintDevice(QWidget *w);
         ~SvgalibPaintDevice();

         void* memory() const { return QScreen::instance()->base(); }

         QPaintEngine *paintEngine() const { return pengine; }
         int metric(PaintDeviceMetric m) const;

     private:
         SvgalibPaintEngine *pengine;
     };

The paintEngine() function should return an instance of the `SvgalibPaintEngine` class. The memory() function should return a pointer to the buffer which should be used when drawing the widget. This driver is drawing directly on the screen without any buffering, and its memory() function returns a pointer to the framebuffer. For this reason, the metric() function is implemented to reflect the metrics of this buffer:

### The SvgalibSurface Class

Then you must create a custom window surface. The QWSWindowSurface class manages the memory used when drawing a widget.

     class SvgalibSurface : public QWSWindowSurface
     {
     public:
         SvgalibSurface();
         SvgalibSurface(QWidget *w);
         ~SvgalibSurface();

         void setGeometry(const QRect &rect);
         QRect geometry() const { return brect; }

         bool isValidFor(const QWidget *) const { return true; }

         void scroll(const QRegion &region, int dx, int dy);

         const QString key() const { return QLatin1String("svgalib"); }
         const QByteArray data() const { return QByteArray(); }

         bool attach(const QByteArray &) { return true; }
         void detach() {}

         const QImage image() const { return QImage(); }
         QPaintDevice *paintDevice() { return pdevice; }
         QPoint painterOffset() const;

     private:
         QRect brect;
         SvgalibPaintDevice *pdevice;
     };

Most of the pure virtual functions inherited from QWSWindowSurface is trivially implemented as inline functions. However the scroll() function actually makes use of some hardware acceleration:

     void SvgalibSurface::scroll(const QRegion &region, int dx, int dy)
     {
         const QVector<QRect> rects = region.rects();
         for (int i = 0; i < rects.size(); ++i) {
             const QRect r = rects.at(i);
             gl_copybox(r.left(), r.top(), r.width(), r.height(),
                        r.left() + dx, r.top() + dy);
         }
     }

### Enable the Screen to Create an Instance of Your Window Surface

Finally, you must make a minor modification to your `SvgalibScreen` class:

     QWSWindowSurface* SvgalibScreen::createSurface(QWidget *widget) const
     {
         static int onScreenPaint = -1;
         if (onScreenPaint == -1)
             onScreenPaint = qgetenv("QT_ONSCREEN_PAINT").toInt();

         if (onScreenPaint > 0 || widget->testAttribute(Qt::WA_PaintOnScreen))
             return new SvgalibSurface(widget);

         return QScreen::createSurface(widget);
     }

     QWSWindowSurface* SvgalibScreen::createSurface(const QString &key) const
     {
         if (key == QLatin1String("svgalib"))
             return new SvgalibSurface;
         return QScreen::createSurface(key);
     }

The createSurface() functions are factory functions that decides what kind of surface a top-level window is using. In this example we only use the `SvgalibSurface` class if the window has the Qt::WA_PaintOnScreen attribute or the environment variable `QT_ONSCREEN_PAINT` is set.