[PATCH 1/2] Capture snapshot stream in anticipation of displaying thumbnails.

Dan McCabe zen3d.linux at gmail.com
Mon Mar 5 17:20:39 PST 2012


This patch sets the stage for displaying frame thumbnails in
qapitrace by first capturing a snaphot stream.

We first prepare qapitrace to capture a snapshot stream. We enhance
replayTrace() to accept a second parameter for specifying thumbnail
capture. This then enables us to notify the retracer so set up command
line parameters to glretrace, which runs out of process. We specify that
glretrace must generate snapshots and then emit them to stdout.

After glretrace returns, we extract PNM images from the output stream.
We parse PNM the ASCII header to extract number channels, the width and
the height of the following binary image data. We then extract the
binary data of the image on a per-scanline copy. We create a QImage of
the correct size and insert the image data into the QImage. Finally, we
scale the size of the image down to the size of a thumbnail.

Lastly, we create a QList collection to hold the sequence of thumbnails.
This list is return to the main window object of qapitrace and will
later be used to display these thumbnails.
---
 common/image.hpp     |    2 +
 common/image_pnm.cpp |   51 ++++++++++++++++++++++++
 gui/main.cpp         |    3 +
 gui/mainwindow.cpp   |   31 ++++++++++++---
 gui/mainwindow.h     |    7 +++-
 gui/retracer.cpp     |  107 +++++++++++++++++++++++++++++++++++++++++++++-----
 gui/retracer.h       |   10 +++++
 7 files changed, 194 insertions(+), 17 deletions(-)

diff --git a/common/image.hpp b/common/image.hpp
index 6316791..ea3eefb 100644
--- a/common/image.hpp
+++ b/common/image.hpp
@@ -108,6 +108,8 @@ bool writePixelsToBuffer(unsigned char *pixels,
 Image *
 readPNG(const char *filename);
 
+const char *
+readPNMHeader(const char *buffer, size_t size, unsigned *channels, unsigned *width, unsigned *height);
 
 } /* namespace image */
 
diff --git a/common/image_pnm.cpp b/common/image_pnm.cpp
index c95f640..f9cd05d 100644
--- a/common/image_pnm.cpp
+++ b/common/image_pnm.cpp
@@ -28,6 +28,7 @@
 #include <assert.h>
 #include <string.h>
 #include <stdint.h>
+#include <stdio.h>
 
 #include "image.hpp"
 
@@ -108,5 +109,55 @@ Image::writePNM(std::ostream &os, const char *comment) const {
     }
 }
 
+const char *
+readPNMHeader(const char *buffer, size_t bufferSize, unsigned *channels, unsigned *width, unsigned *height)
+{
+    *channels = 0;
+    *width = 0;
+    *height = 0;
+
+    const char *currentBuffer = buffer;
+    const char *nextBuffer;
+
+    // parse number of channels
+    int scannedChannels = sscanf(currentBuffer, "P%d\n", channels);
+    if (scannedChannels != 1) { // validate scanning of channels
+        // invalid channel line
+        return buffer;
+    }
+    // convert channel token to number of channels
+    *channels = (*channels == 5) ? 1 : 3;
+
+    // advance past channel line
+    nextBuffer = (const char *) memchr((const void *) currentBuffer, '\n', bufferSize) + 1;
+    bufferSize -= nextBuffer - currentBuffer;
+    currentBuffer = nextBuffer;
+
+    // skip over optional comment
+    if (*currentBuffer == '#') {
+        // advance past comment line
+        nextBuffer = (const char *) memchr((const void *) currentBuffer, '\n', bufferSize) + 1;
+        bufferSize -= nextBuffer - currentBuffer;
+        currentBuffer = nextBuffer;
+    }
+
+    // parse dimensions of image
+    int scannedDimensions = sscanf(currentBuffer, "%d %d\n", width, height);
+    if (scannedDimensions != 2) { // validate scanning of dimensions
+        // invalid dimension line
+        return buffer;
+    }
+
+    // advance past dimension line
+    nextBuffer = (const char *) memchr((const void *) currentBuffer, '\n', bufferSize) + 1;
+    bufferSize -= nextBuffer - currentBuffer;
+    currentBuffer = nextBuffer;
+
+    // skip over "255\n" at end of header
+    nextBuffer = (const char *) memchr((const void *) currentBuffer, '\n', bufferSize) + 1;
+
+    // return start of image data
+    return nextBuffer;
+}
 
 } /* namespace image */
diff --git a/gui/main.cpp b/gui/main.cpp
index d7af53d..18e07f1 100644
--- a/gui/main.cpp
+++ b/gui/main.cpp
@@ -6,12 +6,14 @@
 #include <QApplication>
 #include <QMetaType>
 #include <QVariant>
+#include <QImage>
 
 Q_DECLARE_METATYPE(QList<ApiTraceFrame*>);
 Q_DECLARE_METATYPE(QVector<ApiTraceCall*>);
 Q_DECLARE_METATYPE(Qt::CaseSensitivity);
 Q_DECLARE_METATYPE(ApiTrace::SearchResult);
 Q_DECLARE_METATYPE(ApiTrace::SearchRequest);
+Q_DECLARE_METATYPE(QList<QImage>);
 
 static void usage(void)
 {
@@ -28,6 +30,7 @@ int main(int argc, char **argv)
     qRegisterMetaType<Qt::CaseSensitivity>();
     qRegisterMetaType<ApiTrace::SearchResult>();
     qRegisterMetaType<ApiTrace::SearchRequest>();
+    qRegisterMetaType<QList<QImage> >();
     QStringList args = app.arguments();
 
     int i = 1;
diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp
index d37162b..d1a143d 100644
--- a/gui/mainwindow.cpp
+++ b/gui/mainwindow.cpp
@@ -171,7 +171,7 @@ void MainWindow::replayStart()
             dlgUi.doubleBufferingCB->isChecked());
         m_retracer->setBenchmarking(
             !dlgUi.errorCheckCB->isChecked());
-        replayTrace(false);
+        replayTrace(false, true);
     }
 }
 
@@ -201,6 +201,9 @@ void MainWindow::newTraceFile(const QString &fileName)
         setWindowTitle(
             tr("QApiTrace - %1").arg(info.fileName()));
     }
+
+    // force initial capture of thumbnails
+    replayTrace(false, true);
 }
 
 void MainWindow::replayFinished(const QString &output)
@@ -263,7 +266,7 @@ void MainWindow::finishedLoadingTrace()
     }
 }
 
-void MainWindow::replayTrace(bool dumpState)
+void MainWindow::replayTrace(bool dumpState, bool dumpThumbnails)
 {
     if (m_trace->fileName().isEmpty()) {
         return;
@@ -272,6 +275,7 @@ void MainWindow::replayTrace(bool dumpState)
     m_retracer->setFileName(m_trace->fileName());
     m_retracer->setAPI(m_api);
     m_retracer->setCaptureState(dumpState);
+    m_retracer->setCaptureThumbnails(dumpThumbnails);
     if (m_retracer->captureState() && m_selectedEvent) {
         int index = 0;
         if (m_selectedEvent->type() == ApiTraceEvent::Call) {
@@ -295,9 +299,17 @@ void MainWindow::replayTrace(bool dumpState)
 
     m_ui.actionStop->setEnabled(true);
     m_progressBar->show();
-    if (dumpState) {
-        statusBar()->showMessage(
-            tr("Looking up the state..."));
+    if (dumpState || dumpThumbnails) {
+        if (dumpState && dumpThumbnails) {
+            statusBar()->showMessage(
+                tr("Looking up the state and capturing thumbnails..."));
+        } else if (dumpState) {
+            statusBar()->showMessage(
+                tr("Looking up the state..."));
+        } else if (dumpThumbnails) {
+            statusBar()->showMessage(
+                tr("Capturing thumbnails..."));
+        }
     } else {
         statusBar()->showMessage(
             tr("Replaying the trace file..."));
@@ -321,7 +333,7 @@ void MainWindow::lookupState()
         return;
     }
     m_stateEvent = m_selectedEvent;
-    replayTrace(true);
+    replayTrace(true, false);
 }
 
 MainWindow::~MainWindow()
@@ -736,6 +748,8 @@ void MainWindow::initConnections()
             this, SLOT(replayError(const QString&)));
     connect(m_retracer, SIGNAL(foundState(ApiTraceState*)),
             this, SLOT(replayStateFound(ApiTraceState*)));
+    connect(m_retracer, SIGNAL(foundThumbnails(const QList<QImage>&)),
+            this, SLOT(replayThumbnailsFound(const QList<QImage>&)));
     connect(m_retracer, SIGNAL(retraceErrors(const QList<ApiTraceError>&)),
             this, SLOT(slotRetraceErrors(const QList<ApiTraceError>&)));
 
@@ -831,6 +845,11 @@ void MainWindow::replayStateFound(ApiTraceState *state)
     m_nonDefaultsLookupEvent = 0;
 }
 
+void MainWindow::replayThumbnailsFound(const QList<QImage> &thumbnails)
+{
+    m_thumbnails = thumbnails;
+}
+
 void MainWindow::slotGoTo()
 {
     m_searchWidget->hide();
diff --git a/gui/mainwindow.h b/gui/mainwindow.h
index 59c9ba7..568f692 100644
--- a/gui/mainwindow.h
+++ b/gui/mainwindow.h
@@ -8,6 +8,8 @@
 
 #include <QMainWindow>
 #include <QProcess>
+#include <QList>
+#include <QImage>
 
 class ApiTrace;
 class ApiTraceCall;
@@ -47,6 +49,7 @@ private slots:
     void replayStop();
     void replayFinished(const QString &output);
     void replayStateFound(ApiTraceState *state);
+    void replayThumbnailsFound(const QList<QImage> &thumbnails);
     void replayError(const QString &msg);
     void startedLoadingTrace();
     void loadProgess(int percent);
@@ -85,7 +88,7 @@ private:
     void initObjects();
     void initConnections();
     void newTraceFile(const QString &fileName);
-    void replayTrace(bool dumpState);
+    void replayTrace(bool dumpState, bool dumpThumbnails);
     void fillStateForFrame();
 
     /* there's a difference between selected frame/call and
@@ -115,6 +118,8 @@ private:
 
     ApiTraceEvent *m_stateEvent;
 
+    QList<QImage> m_thumbnails;
+
     Retracer *m_retracer;
 
     VertexDataInterpreter *m_vdataInterpreter;
diff --git a/gui/retracer.cpp b/gui/retracer.cpp
index 17ac14d..59e4093 100644
--- a/gui/retracer.cpp
+++ b/gui/retracer.cpp
@@ -2,8 +2,12 @@
 
 #include "apitracecall.h"
 
+#include "image.hpp"
+
 #include <QDebug>
 #include <QVariant>
+#include <QList>
+#include <QImage>
 
 #include <qjson/parser.h>
 
@@ -83,6 +87,16 @@ void Retracer::setCaptureState(bool enable)
     m_captureState = enable;
 }
 
+bool Retracer::captureThumbnails() const
+{
+    return m_captureThumbnails;
+}
+
+void Retracer::setCaptureThumbnails(bool enable)
+{
+    m_captureThumbnails = enable;
+}
+
 
 void Retracer::run()
 {
@@ -94,6 +108,7 @@ void Retracer::run()
     retrace->setBenchmarking(m_benchmarking);
     retrace->setDoubleBuffered(m_doubleBuffered);
     retrace->setCaptureState(m_captureState);
+    retrace->setCaptureThumbnails(m_captureThumbnails);
     retrace->setCaptureAtCallNumber(m_captureCall);
 
     connect(retrace, SIGNAL(finished(const QString&)),
@@ -106,6 +121,8 @@ void Retracer::run()
             this, SIGNAL(error(const QString&)));
     connect(retrace, SIGNAL(foundState(ApiTraceState*)),
             this, SIGNAL(foundState(ApiTraceState*)));
+    connect(retrace, SIGNAL(foundThumbnails(const QList<QImage>&)),
+            this, SIGNAL(foundThumbnails(const QList<QImage>&)));
     connect(retrace, SIGNAL(retraceErrors(const QList<ApiTraceError>&)),
             this, SIGNAL(retraceErrors(const QList<ApiTraceError>&)));
 
@@ -142,9 +159,15 @@ void RetraceProcess::start()
         arguments << QLatin1String("-sb");
     }
 
-    if (m_captureState) {
-        arguments << QLatin1String("-D");
-        arguments << QString::number(m_captureCall);
+    if (m_captureState || m_captureThumbnails) {
+        if (m_captureState) {
+            arguments << QLatin1String("-D");
+            arguments << QString::number(m_captureCall);
+        }
+        if (m_captureThumbnails) {
+            arguments << QLatin1String("-s"); // emit snapshots
+            arguments << QLatin1String("-"); // emit to stdout
+        }
     } else {
         if (m_benchmarking) {
             arguments << QLatin1String("-b");
@@ -159,7 +182,7 @@ void RetraceProcess::start()
 
 void RetraceProcess::replayFinished(int exitCode, QProcess::ExitStatus exitStatus)
 {
-    QByteArray output = m_process->readAllStandardOutput();
+    QByteArray output;
     QString msg;
     QString errStr = m_process->readAllStandardError();
 
@@ -174,13 +197,67 @@ void RetraceProcess::replayFinished(int exitCode, QProcess::ExitStatus exitStatu
     } else if (exitCode != 0) {
         msg = QLatin1String("Process exited with non zero exit code");
     } else {
-        if (m_captureState) {
-            bool ok = false;
-            QVariantMap parsedJson = m_jsonParser->parse(output, &ok).toMap();
-            ApiTraceState *state = new ApiTraceState(parsedJson);
-            emit foundState(state);
-            msg = tr("State fetched.");
+        if (m_captureState || m_captureThumbnails) {
+            if (m_captureState) {
+                bool ok = false;
+                output = m_process->readAllStandardOutput();
+                QVariantMap parsedJson = m_jsonParser->parse(output, &ok).toMap();
+                ApiTraceState *state = new ApiTraceState(parsedJson);
+                emit foundState(state);
+                msg = tr("State fetched.");
+            }
+            if (m_captureThumbnails) {
+                m_process->setReadChannel(QProcess::StandardOutput);
+
+                QList<QImage> thumbnails;
+
+                while (!m_process->atEnd()) {
+                    unsigned channels = 0;
+                    unsigned width = 0;
+                    unsigned height = 0;
+
+                    char header[512];
+                    qint64 headerSize = 0;
+                    int headerLines = 3; // assume no optional comment line
+
+                    for (int headerLine = 0; headerLine < headerLines; ++headerLine) {
+                        qint64 headerRead = m_process->readLine(&header[headerSize], sizeof(header) - headerSize);
+
+                        // if header actually contains optional comment line, ...
+                        if (headerLine == 1 && header[headerSize] == '#') {
+                            ++headerLines;
+                        }
+
+                        headerSize += headerRead;
+                    }
+
+                    const char *headerEnd = image::readPNMHeader(header, headerSize, &channels, &width, &height);
+
+                    // if invalid PNM header was encountered, ...
+                    if (header == headerEnd) {
+                        qDebug() << "error: invalid snapshot stream encountered\n";
+                        break;
+                    }
+
+                    //qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height << "\n";
+
+                    QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888);
+
+                    int rowBytes = channels * width;
+                    for (int y = 0; y < height; ++y) {
+                        unsigned char *scanLine = snapshot.scanLine(y);
+                        m_process->read((char *) scanLine, rowBytes);
+                    }
+
+                    QImage thumbnail = snapshot.scaled(16, 16, Qt::KeepAspectRatio, Qt::FastTransformation);
+                    thumbnails.append(thumbnail);
+                }
+
+                emit foundThumbnails(thumbnails);
+                msg = tr("Thumbnails fetched.");
+            }
         } else {
+            output = m_process->readAllStandardOutput();
             msg = QString::fromUtf8(output);
         }
     }
@@ -295,6 +372,16 @@ void RetraceProcess::setCaptureState(bool enable)
     m_captureState = enable;
 }
 
+bool RetraceProcess::captureThumbnails() const
+{
+    return m_captureThumbnails;
+}
+
+void RetraceProcess::setCaptureThumbnails(bool enable)
+{
+    m_captureThumbnails = enable;
+}
+
 void RetraceProcess::terminate()
 {
     if (m_process) {
diff --git a/gui/retracer.h b/gui/retracer.h
index e5c391b..ab9e5f3 100644
--- a/gui/retracer.h
+++ b/gui/retracer.h
@@ -40,6 +40,9 @@ public:
     bool captureState() const;
     void setCaptureState(bool enable);
 
+    bool captureThumbnails() const;
+    void setCaptureThumbnails(bool enable);
+
 public slots:
     void start();
     void terminate();
@@ -48,6 +51,7 @@ signals:
     void finished(const QString &output);
     void error(const QString &msg);
     void foundState(ApiTraceState *state);
+    void foundThumbnails(const QList<QImage> &thumbnails);
     void retraceErrors(const QList<ApiTraceError> &errors);
 
 private slots:
@@ -60,6 +64,7 @@ private:
     bool m_benchmarking;
     bool m_doubleBuffered;
     bool m_captureState;
+    bool m_captureThumbnails;
     qlonglong m_captureCall;
 
     QProcess *m_process;
@@ -89,9 +94,13 @@ public:
     bool captureState() const;
     void setCaptureState(bool enable);
 
+    bool captureThumbnails() const;
+    void setCaptureThumbnails(bool enable);
+
 signals:
     void finished(const QString &output);
     void foundState(ApiTraceState *state);
+    void foundThumbnails(const QList<QImage> &humbnails);
     void error(const QString &msg);
     void retraceErrors(const QList<ApiTraceError> &errors);
 
@@ -106,6 +115,7 @@ private:
     bool m_benchmarking;
     bool m_doubleBuffered;
     bool m_captureState;
+    bool m_captureThumbnails;
     qlonglong m_captureCall;
 
     QProcessEnvironment m_processEnvironment;
-- 
1.7.5.4



More information about the apitrace mailing list