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

Dan McCabe zen3d.linux at gmail.com
Wed Feb 29 11:56:27 PST 2012


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

First we set prepare qapitrace to capture a snapshot stream. We enhance
replayTrace() to accept a second parameter for specifying snapshot
dumping. 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 enhance PNM processing to parse 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 create a QList collection to hold the sequence of images.
This list is return to the main window object of qapitrace and will
later be used to display those snapshot images as thumbnails.
---
 common/image.hpp     |    2 +
 common/image_pnm.cpp |   50 +++++++++++++++++++++++++
 gui/mainwindow.cpp   |   33 ++++++++++++++---
 gui/mainwindow.h     |    7 +++-
 gui/retracer.cpp     |   99 +++++++++++++++++++++++++++++++++++++++++++++-----
 gui/retracer.h       |   10 +++++
 6 files changed, 184 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..73a045b 100644
--- a/common/image_pnm.cpp
+++ b/common/image_pnm.cpp
@@ -108,5 +108,55 @@ Image::writePNM(std::ostream &os, const char *comment) const {
     }
 }
 
+const char *
+readPNMHeader(const char *buffer, size_t buffer_size, unsigned *channels, unsigned *width, unsigned *height)
+{
+    *channels = 0;
+    *width = 0;
+    *height = 0;
+
+    const char *curr_buffer = buffer;
+    const char *next_buffer;
+
+    // parse number of channels
+    int scanned_channels = sscanf(curr_buffer, "P%d\n", channels);
+    if (scanned_channels != 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
+    next_buffer = (const char *) memchr((const void *) curr_buffer, '\n', buffer_size) + 1;
+    buffer_size -= next_buffer - curr_buffer;
+    curr_buffer = next_buffer;
+
+    // skip over optional comment
+    if (*curr_buffer == '#') {
+        // advance past comment line
+        next_buffer = (const char *) memchr((const void *) curr_buffer, '\n', buffer_size) + 1;
+        buffer_size -= next_buffer - curr_buffer;
+        curr_buffer = next_buffer;
+    }
+
+    // parse dimensions of image
+    int scanned_dims = sscanf(curr_buffer, "%d %d\n", width, height);
+    if (scanned_dims != 2) { // validate scanning of dimensions
+        // invalid dimension line
+        return buffer;
+    }
+
+    // advance past dimension line
+    next_buffer = (const char *) memchr((const void *) curr_buffer, '\n', buffer_size) + 1;
+    buffer_size -= next_buffer - curr_buffer;
+    curr_buffer = next_buffer;
+
+    // skip over "255\n" at end of header
+    next_buffer = (const char *) memchr((const void *) curr_buffer, '\n', buffer_size) + 1;
+
+    // return start of image data
+    return next_buffer;
+}
 
 } /* namespace image */
diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp
index d37162b..20a32e7 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 snapshots
+    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 dumpSnaps)
 {
     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->setCaptureSnaps(dumpSnaps);
     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 || dumpSnaps) {
+        if (dumpState && dumpSnaps) {
+            statusBar()->showMessage(
+                tr("Looking up the state and capturing thumbnails..."));
+        } else if (dumpState) {
+            statusBar()->showMessage(
+                tr("Looking up the state..."));
+        } else if (dumpSnaps) {
+            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()
@@ -705,6 +717,8 @@ void MainWindow::initObjects()
     m_searchWidget->hide();
 
     m_traceProcess = new TraceProcess(this);
+
+    m_snaps = NULL;
 }
 
 void MainWindow::initConnections()
@@ -736,6 +750,8 @@ void MainWindow::initConnections()
             this, SLOT(replayError(const QString&)));
     connect(m_retracer, SIGNAL(foundState(ApiTraceState*)),
             this, SLOT(replayStateFound(ApiTraceState*)));
+    connect(m_retracer, SIGNAL(foundSnaps(QList<QImage>*)),
+            this, SLOT(replaySnapsFound(QList<QImage>*)));
     connect(m_retracer, SIGNAL(retraceErrors(const QList<ApiTraceError>&)),
             this, SLOT(slotRetraceErrors(const QList<ApiTraceError>&)));
 
@@ -831,6 +847,11 @@ void MainWindow::replayStateFound(ApiTraceState *state)
     m_nonDefaultsLookupEvent = 0;
 }
 
+void MainWindow::replaySnapsFound(QList<QImage> *snaps)
+{
+    m_snaps = snaps;
+}
+
 void MainWindow::slotGoTo()
 {
     m_searchWidget->hide();
diff --git a/gui/mainwindow.h b/gui/mainwindow.h
index 59c9ba7..e6a023f 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 replaySnapsFound(QList<QImage> *snaps);
     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 dumpSnaps);
     void fillStateForFrame();
 
     /* there's a difference between selected frame/call and
@@ -115,6 +118,8 @@ private:
 
     ApiTraceEvent *m_stateEvent;
 
+    QList<QImage> *m_snaps;
+
     Retracer *m_retracer;
 
     VertexDataInterpreter *m_vdataInterpreter;
diff --git a/gui/retracer.cpp b/gui/retracer.cpp
index 17ac14d..5b7afe5 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::captureSnaps() const
+{
+    return m_captureSnaps;
+}
+
+void Retracer::setCaptureSnaps(bool enable)
+{
+    m_captureSnaps = enable;
+}
+
 
 void Retracer::run()
 {
@@ -94,6 +108,7 @@ void Retracer::run()
     retrace->setBenchmarking(m_benchmarking);
     retrace->setDoubleBuffered(m_doubleBuffered);
     retrace->setCaptureState(m_captureState);
+    retrace->setCaptureSnaps(m_captureSnaps);
     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(foundSnaps(QList<QImage>*)),
+            this, SIGNAL(foundSnaps(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_captureSnaps) {
+        if (m_captureState) {
+            arguments << QLatin1String("-D");
+            arguments << QString::number(m_captureCall);
+        }
+        if (m_captureSnaps) {
+            arguments << QLatin1String("-s"); // emit snapshots
+            arguments << QLatin1String("-"); // emit to stdout
+        }
     } else {
         if (m_benchmarking) {
             arguments << QLatin1String("-b");
@@ -160,8 +183,8 @@ void RetraceProcess::start()
 void RetraceProcess::replayFinished(int exitCode, QProcess::ExitStatus exitStatus)
 {
     QByteArray output = m_process->readAllStandardOutput();
-    QString msg;
     QString errStr = m_process->readAllStandardError();
+    QString msg;
 
 #if 0
     qDebug()<<"Process finished = ";
@@ -174,12 +197,58 @@ 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_captureSnaps) {
+            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_captureSnaps) {
+                QList<QImage> *snaps = new QList<QImage>();
+
+                const char *curr_buffer = output.data();
+                const char *next_buffer;
+                int buffer_size = output.count();
+
+                while (buffer_size > 0) {
+                    unsigned channels = 0;
+                    unsigned width = 0;
+                    unsigned height = 0;
+
+                    next_buffer = image::readPNMHeader(curr_buffer, buffer_size, &channels, &width, &height);
+
+                    // if invalid PNM header was encountered, ...
+                    if (next_buffer == curr_buffer) {
+                        qDebug() << "error: invalid snapshot stream encountered\n";
+                        break;
+                    }
+
+                    //qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height << "\n";
+
+                    buffer_size -= next_buffer - curr_buffer;
+                    curr_buffer = next_buffer;
+
+                    QImage snap = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888);
+
+                    const char *src = curr_buffer;
+                    int row_bytes = channels * width;
+                    for (int y = 0; y < height; ++y) {
+                        unsigned char *dst = snap.scanLine(y);
+                        memcpy((void *) dst, (const void *) src, row_bytes);
+                        src += row_bytes;
+                    }
+                    snaps->append(snap);
+
+                    int image_size = row_bytes * height;
+                    curr_buffer += image_size;
+                    buffer_size -= image_size;
+                }
+
+                emit foundSnaps(snaps);
+                msg = tr("Snaps fetched");
+            }
         } else {
             msg = QString::fromUtf8(output);
         }
@@ -295,6 +364,16 @@ void RetraceProcess::setCaptureState(bool enable)
     m_captureState = enable;
 }
 
+bool RetraceProcess::captureSnaps() const
+{
+    return m_captureSnaps;
+}
+
+void RetraceProcess::setCaptureSnaps(bool enable)
+{
+    m_captureSnaps = enable;
+}
+
 void RetraceProcess::terminate()
 {
     if (m_process) {
diff --git a/gui/retracer.h b/gui/retracer.h
index e5c391b..88d29a0 100644
--- a/gui/retracer.h
+++ b/gui/retracer.h
@@ -40,6 +40,9 @@ public:
     bool captureState() const;
     void setCaptureState(bool enable);
 
+    bool captureSnaps() const;
+    void setCaptureSnaps(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 foundSnaps(QList<QImage> *snaps);
     void retraceErrors(const QList<ApiTraceError> &errors);
 
 private slots:
@@ -60,6 +64,7 @@ private:
     bool m_benchmarking;
     bool m_doubleBuffered;
     bool m_captureState;
+    bool m_captureSnaps;
     qlonglong m_captureCall;
 
     QProcess *m_process;
@@ -89,9 +94,13 @@ public:
     bool captureState() const;
     void setCaptureState(bool enable);
 
+    bool captureSnaps() const;
+    void setCaptureSnaps(bool enable);
+
 signals:
     void finished(const QString &output);
     void foundState(ApiTraceState *state);
+    void foundSnaps(QList<QImage> *snaps);
     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_captureSnaps;
     qlonglong m_captureCall;
 
     QProcessEnvironment m_processEnvironment;
-- 
1.7.5.4



More information about the apitrace mailing list