]> git.cworth.org Git - apitrace/blob - gui/retracer.cpp
Capture snapshot stream in anticipation of displaying thumbnails.
[apitrace] / gui / retracer.cpp
1 #include "retracer.h"
2
3 #include "apitracecall.h"
4
5 #include "image.hpp"
6
7 #include <QDebug>
8 #include <QVariant>
9 #include <QList>
10 #include <QImage>
11
12 #include <qjson/parser.h>
13
14 Retracer::Retracer(QObject *parent)
15     : QThread(parent),
16       m_benchmarking(false),
17       m_doubleBuffered(true),
18       m_captureState(false),
19       m_captureCall(0)
20 {
21 #ifdef Q_OS_WIN
22     QString format = QLatin1String("%1;");
23 #else
24     QString format = QLatin1String("%1:");
25 #endif
26     QString buildPath = format.arg(APITRACE_BINARY_DIR);
27     m_processEnvironment = QProcessEnvironment::systemEnvironment();
28     m_processEnvironment.insert("PATH", buildPath +
29                                 m_processEnvironment.value("PATH"));
30
31     qputenv("PATH",
32             m_processEnvironment.value("PATH").toLatin1());
33 }
34
35 QString Retracer::fileName() const
36 {
37     return m_fileName;
38 }
39
40 void Retracer::setFileName(const QString &name)
41 {
42     m_fileName = name;
43 }
44
45 void Retracer::setAPI(trace::API api)
46 {
47     m_api = api;
48 }
49
50 bool Retracer::isBenchmarking() const
51 {
52     return m_benchmarking;
53 }
54
55 void Retracer::setBenchmarking(bool bench)
56 {
57     m_benchmarking = bench;
58 }
59
60 bool Retracer::isDoubleBuffered() const
61 {
62     return m_doubleBuffered;
63 }
64
65 void Retracer::setDoubleBuffered(bool db)
66 {
67     m_doubleBuffered = db;
68 }
69
70 void Retracer::setCaptureAtCallNumber(qlonglong num)
71 {
72     m_captureCall = num;
73 }
74
75 qlonglong Retracer::captureAtCallNumber() const
76 {
77     return m_captureCall;
78 }
79
80 bool Retracer::captureState() const
81 {
82     return m_captureState;
83 }
84
85 void Retracer::setCaptureState(bool enable)
86 {
87     m_captureState = enable;
88 }
89
90 bool Retracer::captureThumbnails() const
91 {
92     return m_captureThumbnails;
93 }
94
95 void Retracer::setCaptureThumbnails(bool enable)
96 {
97     m_captureThumbnails = enable;
98 }
99
100
101 void Retracer::run()
102 {
103     RetraceProcess *retrace = new RetraceProcess();
104     retrace->process()->setProcessEnvironment(m_processEnvironment);
105
106     retrace->setFileName(m_fileName);
107     retrace->setAPI(m_api);
108     retrace->setBenchmarking(m_benchmarking);
109     retrace->setDoubleBuffered(m_doubleBuffered);
110     retrace->setCaptureState(m_captureState);
111     retrace->setCaptureThumbnails(m_captureThumbnails);
112     retrace->setCaptureAtCallNumber(m_captureCall);
113
114     connect(retrace, SIGNAL(finished(const QString&)),
115             this, SLOT(cleanup()));
116     connect(retrace, SIGNAL(error(const QString&)),
117             this, SLOT(cleanup()));
118     connect(retrace, SIGNAL(finished(const QString&)),
119             this, SIGNAL(finished(const QString&)));
120     connect(retrace, SIGNAL(error(const QString&)),
121             this, SIGNAL(error(const QString&)));
122     connect(retrace, SIGNAL(foundState(ApiTraceState*)),
123             this, SIGNAL(foundState(ApiTraceState*)));
124     connect(retrace, SIGNAL(foundThumbnails(const QList<QImage>&)),
125             this, SIGNAL(foundThumbnails(const QList<QImage>&)));
126     connect(retrace, SIGNAL(retraceErrors(const QList<ApiTraceError>&)),
127             this, SIGNAL(retraceErrors(const QList<ApiTraceError>&)));
128
129     retrace->start();
130
131     exec();
132
133     /* means we need to kill the process */
134     if (retrace->process()->state() != QProcess::NotRunning) {
135         retrace->terminate();
136     }
137
138     delete retrace;
139 }
140
141
142 void RetraceProcess::start()
143 {
144     QString prog;
145     QStringList arguments;
146
147     if (m_api == trace::API_GL) {
148         prog = QLatin1String("glretrace");
149     } else if (m_api == trace::API_EGL) {
150         prog = QLatin1String("eglretrace");
151     } else {
152         assert(0);
153         return;
154     }
155
156     if (m_doubleBuffered) {
157         arguments << QLatin1String("-db");
158     } else {
159         arguments << QLatin1String("-sb");
160     }
161
162     if (m_captureState || m_captureThumbnails) {
163         if (m_captureState) {
164             arguments << QLatin1String("-D");
165             arguments << QString::number(m_captureCall);
166         }
167         if (m_captureThumbnails) {
168             arguments << QLatin1String("-s"); // emit snapshots
169             arguments << QLatin1String("-"); // emit to stdout
170         }
171     } else {
172         if (m_benchmarking) {
173             arguments << QLatin1String("-b");
174         }
175     }
176
177     arguments << m_fileName;
178
179     m_process->start(prog, arguments);
180 }
181
182
183 void RetraceProcess::replayFinished(int exitCode, QProcess::ExitStatus exitStatus)
184 {
185     QByteArray output;
186     QString msg;
187     QString errStr = m_process->readAllStandardError();
188
189 #if 0
190     qDebug()<<"Process finished = ";
191     qDebug()<<"\terr = "<<errStr;
192     qDebug()<<"\tout = "<<output;
193 #endif
194
195     if (exitStatus != QProcess::NormalExit) {
196         msg = QLatin1String("Process crashed");
197     } else if (exitCode != 0) {
198         msg = QLatin1String("Process exited with non zero exit code");
199     } else {
200         if (m_captureState || m_captureThumbnails) {
201             if (m_captureState) {
202                 bool ok = false;
203                 output = m_process->readAllStandardOutput();
204                 QVariantMap parsedJson = m_jsonParser->parse(output, &ok).toMap();
205                 ApiTraceState *state = new ApiTraceState(parsedJson);
206                 emit foundState(state);
207                 msg = tr("State fetched.");
208             }
209             if (m_captureThumbnails) {
210                 m_process->setReadChannel(QProcess::StandardOutput);
211
212                 QList<QImage> thumbnails;
213
214                 while (!m_process->atEnd()) {
215                     unsigned channels = 0;
216                     unsigned width = 0;
217                     unsigned height = 0;
218
219                     char header[512];
220                     qint64 headerSize = 0;
221                     int headerLines = 3; // assume no optional comment line
222
223                     for (int headerLine = 0; headerLine < headerLines; ++headerLine) {
224                         qint64 headerRead = m_process->readLine(&header[headerSize], sizeof(header) - headerSize);
225
226                         // if header actually contains optional comment line, ...
227                         if (headerLine == 1 && header[headerSize] == '#') {
228                             ++headerLines;
229                         }
230
231                         headerSize += headerRead;
232                     }
233
234                     const char *headerEnd = image::readPNMHeader(header, headerSize, &channels, &width, &height);
235
236                     // if invalid PNM header was encountered, ...
237                     if (header == headerEnd) {
238                         qDebug() << "error: invalid snapshot stream encountered\n";
239                         break;
240                     }
241
242                     //qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height << "\n";
243
244                     QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888);
245
246                     int rowBytes = channels * width;
247                     for (int y = 0; y < height; ++y) {
248                         unsigned char *scanLine = snapshot.scanLine(y);
249                         m_process->read((char *) scanLine, rowBytes);
250                     }
251
252                     QImage thumbnail = snapshot.scaled(16, 16, Qt::KeepAspectRatio, Qt::FastTransformation);
253                     thumbnails.append(thumbnail);
254                 }
255
256                 emit foundThumbnails(thumbnails);
257                 msg = tr("Thumbnails fetched.");
258             }
259         } else {
260             output = m_process->readAllStandardOutput();
261             msg = QString::fromUtf8(output);
262         }
263     }
264
265     QStringList errorLines = errStr.split('\n');
266     QList<ApiTraceError> errors;
267     QRegExp regexp("(^\\d+): +(\\b\\w+\\b): (.+$)");
268     foreach(QString line, errorLines) {
269         if (regexp.indexIn(line) != -1) {
270             ApiTraceError error;
271             error.callIndex = regexp.cap(1).toInt();
272             error.type = regexp.cap(2);
273             error.message = regexp.cap(3);
274             errors.append(error);
275         }
276     }
277     if (!errors.isEmpty()) {
278         emit retraceErrors(errors);
279     }
280     emit finished(msg);
281 }
282
283 void RetraceProcess::replayError(QProcess::ProcessError err)
284 {
285     /*
286      * XXX: this function is likely unnecessary and should be eliminated given
287      * that replayFinished is always called, even on errors.
288      */
289
290 #if 0
291     qDebug()<<"Process error = "<<err;
292     qDebug()<<"\terr = "<<m_process->readAllStandardError();
293     qDebug()<<"\tout = "<<m_process->readAllStandardOutput();
294 #endif
295
296     emit error(
297         tr("Couldn't execute the replay file '%1'").arg(m_fileName));
298 }
299
300 Q_DECLARE_METATYPE(QList<ApiTraceError>);
301 RetraceProcess::RetraceProcess(QObject *parent)
302     : QObject(parent)
303 {
304     m_process = new QProcess(this);
305     m_jsonParser = new QJson::Parser();
306
307     qRegisterMetaType<QList<ApiTraceError> >();
308
309     connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)),
310             this, SLOT(replayFinished(int, QProcess::ExitStatus)));
311     connect(m_process, SIGNAL(error(QProcess::ProcessError)),
312             this, SLOT(replayError(QProcess::ProcessError)));
313 }
314
315 QProcess * RetraceProcess::process() const
316 {
317     return m_process;
318 }
319
320 QString RetraceProcess::fileName() const
321 {
322     return m_fileName;
323 }
324
325 void RetraceProcess::setFileName(const QString &name)
326 {
327     m_fileName = name;
328 }
329
330 void RetraceProcess::setAPI(trace::API api)
331 {
332     m_api = api;
333 }
334
335 bool RetraceProcess::isBenchmarking() const
336 {
337     return m_benchmarking;
338 }
339
340 void RetraceProcess::setBenchmarking(bool bench)
341 {
342     m_benchmarking = bench;
343 }
344
345 bool RetraceProcess::isDoubleBuffered() const
346 {
347     return m_doubleBuffered;
348 }
349
350 void RetraceProcess::setDoubleBuffered(bool db)
351 {
352     m_doubleBuffered = db;
353 }
354
355 void RetraceProcess::setCaptureAtCallNumber(qlonglong num)
356 {
357     m_captureCall = num;
358 }
359
360 qlonglong RetraceProcess::captureAtCallNumber() const
361 {
362     return m_captureCall;
363 }
364
365 bool RetraceProcess::captureState() const
366 {
367     return m_captureState;
368 }
369
370 void RetraceProcess::setCaptureState(bool enable)
371 {
372     m_captureState = enable;
373 }
374
375 bool RetraceProcess::captureThumbnails() const
376 {
377     return m_captureThumbnails;
378 }
379
380 void RetraceProcess::setCaptureThumbnails(bool enable)
381 {
382     m_captureThumbnails = enable;
383 }
384
385 void RetraceProcess::terminate()
386 {
387     if (m_process) {
388         m_process->terminate();
389         emit finished(tr("Process terminated."));
390     }
391 }
392
393 void Retracer::cleanup()
394 {
395     quit();
396 }
397
398 RetraceProcess::~RetraceProcess()
399 {
400     delete m_jsonParser;
401 }
402
403 #include "retracer.moc"