Qt for embedded Linux and Linux FB

當 Qt for embedded Linux 使用 Linux FB 做輸出時,它的流程為:

首先我們知道 Qt for embedded Linux 是由 QWS server 來做繪圖動作,這個動作是由 QScreen 來做的。在 QScreen::exposeRegion 函式中會產生一 QImage,然後呼叫 QScreen::compose 的動作。QScreen::compose 會收集指定區域內所有的 QWSWindowSurface,將它們畫入 QImage 中。然後再透過 QScreen::blit 函式來輸出到 framebuffer。
我們來看看 QScreen::blit 函式:
void QScreen::blit(const QImage &img, const QPoint &topLeft, const QRegion &reg)
{
    const QRect bound = (region() & QRect(topLeft, img.size())).boundingRect();
    QWSDisplay::grab();
    d_ptr->blit(this, img, topLeft - offset(),
            (reg & bound).translated(-topLeft));
    QWSDisplay::ungrab();
}
在真正呼叫 d_ptr->blit 之前,它會取得每一個 QWSDisplay 的 lock(使用 semaphore 實作),然後再 blit。blit 是一個 function pointer,它會根據 framebuffer 與 QImage 的 color depth 而呼叫不同的函式。因為我們 framebuffer depth 是 16,這邊應該是呼叫 blit_16:
static void blit_16(QScreen *screen, const QImage &image,
                    const QPoint &topLeft, const QRegion &region)
{
    switch (image.format()) {
    case QImage::Format_RGB32:
    case QImage::Format_ARGB32:
    case QImage::Format_ARGB32_Premultiplied:
        // ### This probably doesn't work but it's a case which should never happen
        blit_template<quint16, quint32>(screen, image, topLeft, region);
        return;
    case QImage::Format_RGB16:
        blit_template<quint16, quint16>(screen, image, topLeft, region);
        return;
    default:
        qCritical("blit_16(): Image format %d not supported!", image.format());
    }
}
這邊會再接著叫 blit_template:
template <typename DST, typename SRC>
static void blit_template(QScreen *screen, const QImage &image,
                          const QPoint &topLeft, const QRegion &region)
{
    DST *dest = reinterpret_cast<DST*>(screen->base());
    const int screenStride = screen->linestep();
    const int imageStride = image.bytesPerLine();

    if (region.rectCount() == 1) {
        const QRect r = region.boundingRect();
        const SRC *src = reinterpret_cast<const SRC*>(image.scanLine(r.y()))
                         + r.x();
        qt_rectconvert<DST, SRC>(dest, src,
                                 r.x() + topLeft.x(), r.y() + topLeft.y(),
                                 r.width(), r.height(),
                                 screenStride, imageStride);
    } else {
        const QVector<QRect> rects = region.rects();

        for (int i = 0; i < rects.size(); ++i) {
            const QRect r = rects.at(i);
            const SRC *src = reinterpret_cast<const SRC*>(image.scanLine(r.y()))
                             + r.x();
            qt_rectconvert<DST, SRC>(dest, src,
                                     r.x() + topLeft.x(), r.y() + topLeft.y(),
                                     r.width(), r.height(),
                                     screenStride, imageStride);
        }
    }
}
上面看到最關鍵的是 qt_rectconvert 這個 template。根據 DST/SRC 的不同,而會有不同的做法。在我們的 case DST/SRC 是一樣的:
#define QT_RECTCONVERT_TRIVIAL_IMPL(T)                                  \
    template <>                                                         \
    inline void qt_rectconvert(T *dest, const T *src,                   \
                               int x, int y, int width, int height,     \
                               int dstStride, int srcStride)            \
    {                                                                   \
        qt_rectcopy(dest, src, x, y, width, height, dstStride, srcStride); \
    }
QT_RECTCONVERT_TRIVIAL_IMPL(quint32)
QT_RECTCONVERT_TRIVIAL_IMPL(qrgb888)
QT_RECTCONVERT_TRIVIAL_IMPL(qargb6666)
QT_RECTCONVERT_TRIVIAL_IMPL(qrgb666)
QT_RECTCONVERT_TRIVIAL_IMPL(qrgb565)
QT_RECTCONVERT_TRIVIAL_IMPL(qargb8565)
QT_RECTCONVERT_TRIVIAL_IMPL(quint16)
QT_RECTCONVERT_TRIVIAL_IMPL(qargb8555)
QT_RECTCONVERT_TRIVIAL_IMPL(qrgb555)
QT_RECTCONVERT_TRIVIAL_IMPL(qargb4444)
QT_RECTCONVERT_TRIVIAL_IMPL(qrgb444)
上面說明了當 DST/SRC 是一樣時,qt_rectconvert 直接呼叫 qt_rectcopy:
inline void qt_rectcopy(T *dest, const T *src,
                        int x, int y, int width, int height,
                        int dstStride, int srcStride)
{
    char *d = (char*)(dest + x) + y * dstStride;
    const char *s = (char*)(src);
    for (int i = 0; i < height; ++i) {
        ::memcpy(d, s, width * sizeof(T));
        d += dstStride;
        s += srcStride;
    }
}
而 qt_rectcopy 直接一行一行呼叫 memcpy。
那如果 DST/SRC 不同的情況呢?這時 qt_rectconvert 內容為:
template <class DST, class SRC>
inline void qt_rectconvert(DST *dest, const SRC *src,
                           int x, int y, int width, int height,
                           int dstStride, int srcStride)
{
    char *d = (char*)(dest + x) + y * dstStride;
    const char *s = (char*)(src);
    for (int i = 0; i < height; ++i) {
        qt_memconvert<DST,SRC>((DST*)d, (const SRC*)s, width);
        d += dstStride;
        s += srcStride;
    }
}
它呼叫了 qt_memconvert。由名稱來看可以理解,不同的 color depth 之間要轉換,才能丟到 framebuffer 去。
回過頭來說,QScreen 初始化 framebuffer 的方式很平常,就是打開 /dev/fb0 然後透過 mmap 取得一 memory address。之後直接對此 memory address 做 blit。
上述的過程都在 QLinuxFbScreen::connect 中完成(QLinuxFbScreen 繼承了 QScreen):
bool QLinuxFbScreen::connect(const QString &displaySpec)
{
    QString dev = QLatin1String("/dev/fb0");
    ...
    if (access(dev.toLatin1().constData(), R_OK|W_OK) == 0)
        d_ptr->fd = QT_OPEN(dev.toLatin1().constData(), O_RDWR);
    ...
    data = (unsigned char *)mmap(0, mapsize, PROT_READ | PROT_WRITE,
                                     MAP_SHARED, d_ptr->fd, 0);
}

留言

熱門文章