From: Dan McCabe Date: Tue, 6 Mar 2012 01:20:39 +0000 (-0800) Subject: Capture snapshot stream in anticipation of displaying thumbnails. X-Git-Url: https://git.cworth.org/git?a=commitdiff_plain;h=66dfdda1767b11e132c259adb6c1872dc2e1fff6;p=apitrace Capture snapshot stream in anticipation of displaying thumbnails. 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. --- 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 #include #include +#include #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 #include #include +#include Q_DECLARE_METATYPE(QList); Q_DECLARE_METATYPE(QVector); Q_DECLARE_METATYPE(Qt::CaseSensitivity); Q_DECLARE_METATYPE(ApiTrace::SearchResult); Q_DECLARE_METATYPE(ApiTrace::SearchRequest); +Q_DECLARE_METATYPE(QList); static void usage(void) { @@ -28,6 +30,7 @@ int main(int argc, char **argv) qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType >(); QStringList args = app.arguments(); int i = 1; diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 672ad64..9a177f6 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -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&)), + this, SLOT(replayThumbnailsFound(const QList&))); connect(m_retracer, SIGNAL(retraceErrors(const QList&)), this, SLOT(slotRetraceErrors(const QList&))); @@ -832,6 +846,11 @@ void MainWindow::replayStateFound(ApiTraceState *state) m_nonDefaultsLookupEvent = 0; } +void MainWindow::replayThumbnailsFound(const QList &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 #include +#include +#include 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 &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 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 #include +#include +#include #include @@ -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&)), + this, SIGNAL(foundThumbnails(const QList&))); connect(retrace, SIGNAL(retraceErrors(const QList&)), this, SIGNAL(retraceErrors(const QList&))); @@ -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 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 &thumbnails); void retraceErrors(const QList &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 &humbnails); void error(const QString &msg); void retraceErrors(const QList &errors); @@ -106,6 +115,7 @@ private: bool m_benchmarking; bool m_doubleBuffered; bool m_captureState; + bool m_captureThumbnails; qlonglong m_captureCall; QProcessEnvironment m_processEnvironment;