]> git.cworth.org Git - apitrace/commitdiff
Capture snapshot stream in anticipation of displaying thumbnails.
authorDan McCabe <zen3d.linux@gmail.com>
Tue, 6 Mar 2012 01:20:39 +0000 (17:20 -0800)
committerJosé Fonseca <jose.r.fonseca@gmail.com>
Sun, 11 Mar 2012 14:23:55 +0000 (14:23 +0000)
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
common/image_pnm.cpp
gui/main.cpp
gui/mainwindow.cpp
gui/mainwindow.h
gui/retracer.cpp
gui/retracer.h

index 6316791bba210763dd79d10ed52afd6a20bfef9c..ea3eefb0d589c6e3f342860b8a1bd9720b3011bf 100644 (file)
@@ -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 */
 
index c95f640fcc1ee6ff69af318f3bc52ba0d4edf1a8..f9cd05d1fbc7593dafcb6e5bf77d48beb7505a1d 100644 (file)
@@ -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 */
index d7af53dde789c219e76dae1428254f3b58337dfd..18e07f1ffcb87275aaa777184a22fb839c31f2fd 100644 (file)
@@ -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;
index 672ad643b42713a3965f302ee4c7e9f1d1a06c4b..9a177f6a598d000e4bab4e27fd272e74f3abb8bf 100644 (file)
@@ -172,7 +172,7 @@ void MainWindow::replayStart()
             dlgUi.doubleBufferingCB->isChecked());
         m_retracer->setBenchmarking(
             !dlgUi.errorCheckCB->isChecked());
-        replayTrace(false);
+        replayTrace(false, true);
     }
 }
 
@@ -202,6 +202,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)
@@ -264,7 +267,7 @@ void MainWindow::finishedLoadingTrace()
     }
 }
 
-void MainWindow::replayTrace(bool dumpState)
+void MainWindow::replayTrace(bool dumpState, bool dumpThumbnails)
 {
     if (m_trace->fileName().isEmpty()) {
         return;
@@ -273,6 +276,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) {
@@ -296,9 +300,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..."));
@@ -322,7 +334,7 @@ void MainWindow::lookupState()
         return;
     }
     m_stateEvent = m_selectedEvent;
-    replayTrace(true);
+    replayTrace(true, false);
 }
 
 MainWindow::~MainWindow()
@@ -737,6 +749,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>&)));
 
@@ -832,6 +846,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();
index 59c9ba78010f6bcd0e0ab9a5f2b43d4d19062daf..568f6925c513f12db74e91a20806d1e4117e9384 100644 (file)
@@ -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;
index 17ac14d7d8d13a97da4f2e1a61455770e9b1104d..59e4093c0e09dd3af34183d891ce33b20ce8430d 100644 (file)
@@ -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) {
index e5c391bcb23e91a7b5169601910589dac3d54770..ab9e5f3a1515ab1d85fe20f2fa339360dfc6da12 100644 (file)
@@ -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;