當 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 ®)
{
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 ®ion)
{
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 ®ion)
{
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);
}
留言
張貼留言