Viadeo Twitter Google Bookmarks ! Facebook Digg del.icio.us MySpace Yahoo MyWeb Blinklist Netvouz Reddit Simpy StumbleUpon Bookmarks Windows Live Favorites 
Logo Documentation Qt ·  Page d'accueil  ·  Toutes les classes  ·  Toutes les fonctions  ·  Vues d'ensemble  · 

Accelerated Graphics Driver Example

Files:

Warning: This example was designed to work with Qt 4.4 and will not work with current versions of Qt. It will be removed from Qt 4.7.

The Accelerated Graphics Driver example shows how you can write your own accelerated graphics driver and add your graphics driver to Qt for Embedded Linux. In Qt for Embedded Linux, painting is a pure software implementation and is normally performed in two steps: The clients render each window onto a corresponding surface (stored in memory) using a paint engine, and then the server uses the graphics driver to compose the surface images and copy them to the screen. (See the Qt for Embedded Linux Architecture documentation for details.)

The rendering can be accelerated in two ways: Either by accelerating the copying of pixels to the screen, or by accelerating the explicit painting operations. The first is done in the graphics driver implementation, the latter is performed by the paint engine implementation. Typically, both the pixel copying and the painting operations are accelerated using the following approach:

  1. Creating a Custom Graphics Driver
  2. Implementing a Custom Paint Engine
  3. Making the Widgets Aware of the Custom Paint Engine

After compiling the example code, install the graphics driver plugin with the command make install. To start an application using the graphics driver, you can either set the environment variable QWS_DISPLAY and then run the application, or you can just run the application using the -display switch:

 myApplication -qws -display svgalib

SVGAlib
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 cards used on desktop computers. It should work on most workstations and has a small and simple API.

Step 1: Creating a Custom Graphics Driver

The custom graphics driver is created by deriving from the QScreen class. QScreen is the base class for implementing screen/graphics drivers in Qt for Embedded Linux.

 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 blit(const QImage &img, const QPoint &topLeft, const QRegion &region);
     void solidFill(const QColor &color, const QRegion &region);

 private:
     void initColorMap();
     void blit16To8(const QImage &image,
                    const QPoint &topLeft, const QRegion &region);
     void blit32To8(const QImage &image,
                    const QPoint &topLeft, const QRegion &region);

     GraphicsContext *context;
 };

The connect(), disconnect(), initDevice() and shutdownDevice() functions are declared as pure virtual functions in QScreen and must be implemented. They are used to configure the hardware, or query its configuration: connect() and disconnect() are called by both the server and client processes, while the initDevice() and shutdownDevice() functions are only called by the server process.

QScreen's setMode() and blank() functions are also pure virtual, but our driver's implementations are trivial. The last two functions (blit() and solidFill()) are the ones involved in putting pixels on the screen, i.e., we reimplement these functions to perform the pixel copying acceleration.

Finally, the context variable is a pointer to a SVGAlib specific type. Note that the details of using the SVGAlib library is beyond the scope of this example.

SvgalibScreen Class Implementation

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

 bool SvgalibScreen::connect(const QString &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 = getModeDepth(modeinfo);
     QScreen::size = QScreen::lstep * dh;
     QScreen::data = 0;

     switch (depth()) {
     case 32:
         setPixelFormat(QImage::Format_ARGB32_Premultiplied);
         break;
     case 24:
         setPixelFormat(QImage::Format_RGB888);
         break;
     case 16:
         setPixelFormat(QImage::Format_RGB16);
         break;
     case 15:
         setPixelFormat(QImage::Format_RGB555);
         break;
     default:
         break;
     }

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

     const QStringList args = displaySpec.split(QLatin1Char(':'),
                                                QString::SkipEmptyParts);
     grayscale = args.contains(QLatin1String("grayscale"), Qt::CaseInsensitive);

     return true;
 }

It is important that the connect() function initializes the data, lstep, w, h, dw, dh, d, physWidth and physHeight variables (inherited from QScreen) to ensure that the driver is in a state consistent with the driver configuration.

In this particular 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)
         QScreen::data = vga_getgraphmem();

     initColorMap();

     QScreenCursor::initSoftwareCursor();
     return true;
 }

When the connect() function returns, the server process calls the initDevice() function which is expected to do the necessary hardware initialization, leaving the hardware in a state consistent with the driver configuration.

Note that we have chosen to use the software cursor. 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);
 }

 void SvgalibScreen::disconnect()
 {
 }

Before exiting, the server process will call the shutdownDevice() function to do the necessary hardware cleanup. Again, it is important that the function leaves the hardware in a state consistent with the driver configuration. When shutdownDevice() returns, the disconnect() function is called. Our implementation of the latter function is trivial.

Note that, provided that the QScreen::data variable points to a valid linear framebuffer, the graphics driver is fully functional as a simple screen driver at this point. The rest of this example will show where to take advantage of the accelerated capabilities available on the hardware.

Whenever an area on the screen needs to be updated, the server will call the exposeRegion() function that paints the given region on screen. 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 the driver so we do not reimplement exposeRegion().

To control how the pixels are put onto the screen we need to reimplement the solidFill() and blit() functions.

 void SvgalibScreen::solidFill(const QColor &color, const QRegion &reg)
 {
     int c;
     if (depth() == 4 || depth() == 8)
         c = alloc(color.red(), color.green(), color.blue());
     else
         c = gl_rgbcolor(color.red(), color.green(), color.blue());

     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(), c);
     }
 }

 void SvgalibScreen::blit(const QImage &img, const QPoint &topLeft,
                          const QRegion &reg)
 {
     if (depth() == 8) {
         switch (img.format()) {
         case QImage::Format_RGB16:
             blit16To8(img, topLeft, reg);
             return;
         case QImage::Format_RGB32:
         case QImage::Format_ARGB32:
         case QImage::Format_ARGB32_Premultiplied:
             blit32To8(img, topLeft, reg);
             return;
         default:
             break;
         }
     }

     if (img.format() != pixelFormat()) {
         if (base())
             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: Implementing a Custom Raster Paint Engine

Qt for Embedded Linux uses QRasterPaintEngine (a raster-based implementation of QPaintEngine) to implement the painting operations.

Acceleration of the painting operations is done by deriving from QRasterPaintEngine class. This is a powerful mechanism for accelerating graphic primitives while getting software fallbacks for all the primitives you do not accelerate.

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

In this example, we will only accelerate one of the drawRects() functions, i.e., only non-rotated, aliased and opaque rectangles will be rendered using accelerated painting. All other primitives are rendered using the base class's unaccelerated implementation.

The paint engine's state is stored in the private member variables, and we reimplement the updateState() function to ensure that our custom paint engine's state is updated properly whenever it is required. The private setClip() and updateClip() functions are only helper function used to simplify the updateState() implementation.

We also reimplement QRasterPaintEngine's begin() and end() functions to initialize the paint engine and to do the cleanup when we are done rendering, respectively.

Private Header Files
Note the include statement used by this class. The files prefixed with private/ are private headers file within Qt for Embedded Linux. Private header files are not part of the standard installation and are only present while compiling Qt. To be able to compile using private header files you need to use a qmake binary within a compiled Qt for Embedded Linux package.

Warning: Private header files may change without notice between releases.

The begin() function initializes the internal state of the paint engine. Note that it also calls the base class implementation to initialize the parts inherited from 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()));
     opaque = true;
     aliased = true;
     sourceOver = true;

     return QRasterPaintEngine::begin(dev);
 }

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

The implementation of the end() function removes the clipping constraints that might have been set in SVGAlib, before calling the corresponding base class implementation.

 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 our custom paint engine's state. The QPaintEngineState class provides information about the active paint engine's current state.

Note that we only accept and save the current matrix if it doesn't do any shearing. 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 QPaintEngine::updateState() function is called to update the parts inherited from the base class.

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

 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 setClip() helper function is called from our custom implementation of updateState(), and enables clipping to the given region. An empty region means that clipping is disabled.

Our custom update function also makes use of the updateClip() helper function that 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, we accelerated that drawing of non-rotated, aliased and opaque rectangles in our reimplementation of the drawRects() function. The QRasterPaintEngine fallback is used whenever the rectangle is not simple enough.

Step 3: Making the Widgets Aware of the Custom Paint Engine

To activate the custom paint engine, we also need to implement a corresponding paint device and window surface and make some minor adjustments of the graphics driver.

Implementing a Custom Paint Device

The custom paint device can be derived from the QCustomRasterPaintDevice class. Reimplement its paintEngine() and memory() functions to activate the accelerated paint engine:

 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.

Our example driver is rendering directly to the screen without any buffering, i.e., our custom pain device's memory() function returns a pointer to the framebuffer. For this reason, we must also reimplement the metric() function to reflect the metrics of framebuffer.

Implementing a Custom Window Surface

The custom window surface can be derived from the QWSWindowSurface class. QWSWindowSurface manages the memory used when drawing a window.

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

     void setGeometry(const QRect &rect);
     bool isValid() const { return true; }
     bool scroll(const QRegion &region, int dx, int dy);
     QString key() const { return QLatin1String("svgalib"); }

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

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

 private:
     SvgalibPaintDevice *pdevice;
 };

We can implement most of the pure virtual functions inherited from QWSWindowSurface as trivial inline functions, except the scroll() function that actually makes use of some hardware acceleration:

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

     return true;
 }

Adjusting the Graphics Driver

Finally, we enable the graphics driver to recognize an instance of our custom window surface:

 QWSWindowSurface* SvgalibScreen::createSurface(QWidget *widget) const
 {
     if (base()) {
         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 determines what kind of surface a top-level window is using. In our example we only use the custom surface if the given window has the Qt::WA_PaintOnScreen attribute or the QT_ONSCREEN_PAINT environment variable is set.

Publicité

Best Of

Actualités les plus lues

Semaine
Mois
Année
  1. « Quelque chose ne va vraiment pas avec les développeurs "modernes" », un développeur à "l'ancienne" critique la multiplication des bibliothèques 103
  2. Pourquoi les programmeurs sont-ils moins payés que les gestionnaires de programmes ? Manquent-ils de pouvoir de négociation ? 56
  3. «Le projet de loi des droits du développeur» : quelles conditions doivent remplir les entreprises pour que le développeur puisse réussir ? 93
  4. Les développeurs détestent-ils les antivirus ? Un programmeur manifeste sa haine envers ces solutions de sécurité 32
  5. Qt Commercial : Digia organise un webinar gratuit le 27 mars sur la conception d'interfaces utilisateur et d'applications avec le framework 0
  6. Quelles nouveautés de C++11 Visual C++ doit-il rapidement intégrer ? Donnez-nous votre avis 10
  7. 2017 : un quinquennat pour une nouvelle version du C++ ? Possible, selon Herb Sutter 11
Page suivante
  1. Linus Torvalds : le "C++ est un langage horrible", en justifiant le choix du C pour le système de gestion de version Git 100
  2. Comment prendre en compte l'utilisateur dans vos applications ? Pour un développeur, « 90 % des utilisateurs sont des idiots » 231
  3. Quel est LE livre que tout développeur doit lire absolument ? Celui qui vous a le plus marqué et inspiré 96
  4. Apple cède et s'engage à payer des droits à Nokia, le conflit des brevets entre les deux firmes s'achève 158
  5. Nokia porte à nouveau plainte contre Apple pour violation de sept nouveaux brevets 158
  6. « Quelque chose ne va vraiment pas avec les développeurs "modernes" », un développeur à "l'ancienne" critique la multiplication des bibliothèques 103
  7. Quel est le code dont vous êtes le plus fier ? Pourquoi l'avez-vous écrit ? Et pourquoi vous a-t-il donné autant de satisfaction ? 83
Page suivante

Le blog Digia au hasard

Logo

Créer des applications avec un style Metro avec Qt, exemples en QML et C++, un article de Digia Qt traduit par Thibaut Cuvelier

Le blog Digia est l'endroit privilégié pour la communication sur l'édition commerciale de Qt, où des réponses publiques sont apportées aux questions les plus posées au support. Lire l'article.

Communauté

Ressources

Liens utiles

Contact

  • Vous souhaitez rejoindre la rédaction ou proposer un tutoriel, une traduction, une question... ? Postez dans le forum Contribuez ou contactez-nous par MP ou par email (voir en bas de page).

Qt dans le magazine

Cette page est une traduction d'une page de la documentation de Qt, écrite par Nokia Corporation and/or its subsidiary(-ies). Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia. Qt 4.6-snapshot
Copyright © 2012 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon, vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.
Vous avez déniché une erreur ? Un bug ? Une redirection cassée ? Ou tout autre problème, quel qu'il soit ? Ou bien vous désirez participer à ce projet de traduction ? N'hésitez pas à nous contacter ou par MP !
 
 
 
 
Partenaires

Hébergement Web