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