]> git.cworth.org Git - apitrace/blob - gui/retracer.cpp
qapitrace: Adjust PATH only once and for all.
[apitrace] / gui / retracer.cpp
1 #include "retracer.h"
2
3 #include "apitracecall.h"
4 #include "thumbnail.h"
5
6 #include "image/image.hpp"
7
8 #include "trace_profiler.hpp"
9
10 #include <QDebug>
11 #include <QVariant>
12 #include <QList>
13 #include <QImage>
14
15 #include <qjson/parser.h>
16
17 /**
18  * Wrapper around a QProcess which enforces IO to block .
19  *
20  * Several QIODevice users (notably QJSON) expect blocking semantics, e.g.,
21  * they expect that QIODevice::read() will blocked until the requested ammount
22  * of bytes is read or end of file is reached. But by default QProcess, does
23  * not block.  And passing QIODevice::Unbuffered mitigates but does not fully
24  * address the problem either.
25  *
26  * This class wraps around QProcess, providing QIODevice interface, while
27  * ensuring that all reads block.
28  *
29  * This class also works around a bug in QProcess::atEnd() implementation.
30  *
31  * See also:
32  * - http://qt-project.org/wiki/Simple_Crypt_IO_Device
33  * - http://qt-project.org/wiki/Custom_IO_Device
34  */
35 class BlockingIODevice : public QIODevice
36 {
37     /* We don't use the Q_OBJECT in this class given we don't declare any
38      * signals and slots or use any other services provided by Qt's meta-object
39      * system. */
40 public:
41     BlockingIODevice(QProcess * io);
42     bool isSequential() const;
43     bool atEnd() const;
44     bool waitForReadyRead(int msecs = -1);
45
46 protected:
47     qint64 readData(char * data, qint64 maxSize);
48     qint64 writeData(const char * data, qint64 maxSize);
49
50 private:
51     QProcess *m_device;
52 };
53
54 BlockingIODevice::BlockingIODevice(QProcess * io) :
55     m_device(io)
56 {
57     /*
58      * We pass QIODevice::Unbuffered to prevent the base QIODevice class to do
59      * its own buffering on top of the overridden readData() method.
60      *
61      * The only buffering used will be to satisfy QIODevice::peek() and
62      * QIODevice::ungetChar().
63      */
64     setOpenMode(ReadOnly | Unbuffered);
65 }
66
67 bool BlockingIODevice::isSequential() const
68 {
69     return true;
70 }
71
72 bool BlockingIODevice::atEnd() const
73 {
74     /*
75      * XXX: QProcess::atEnd() documentation is wrong -- it will return true
76      * even when the process is running --, so we try to workaround that here.
77      */
78     if (m_device->atEnd()) {
79         if (m_device->state() == QProcess::Running) {
80             if (!m_device->waitForReadyRead(-1)) {
81                 return true;
82             }
83         }
84     }
85     return false;
86 }
87
88 bool BlockingIODevice::waitForReadyRead(int msecs)
89 {
90     Q_UNUSED(msecs);
91     return true;
92 }
93
94 qint64 BlockingIODevice::readData(char * data, qint64 maxSize)
95 {
96     qint64 bytesToRead = maxSize;
97     qint64 readSoFar = 0;
98     do {
99         qint64 chunkSize = m_device->read(data + readSoFar, bytesToRead);
100         if (chunkSize < 0) {
101             if (readSoFar) {
102                 return readSoFar;
103             } else {
104                 return chunkSize;
105             }
106         }
107         Q_ASSERT(chunkSize <= bytesToRead);
108         bytesToRead -= chunkSize;
109         readSoFar += chunkSize;
110         if (bytesToRead) {
111             if (!m_device->waitForReadyRead(-1)) {
112                 qDebug() << "waitForReadyRead failed\n";
113                 break;
114             }
115         }
116     } while(bytesToRead);
117
118     return readSoFar;
119 }
120
121 qint64 BlockingIODevice::writeData(const char * data, qint64 maxSize)
122 {
123     Q_ASSERT(false);
124     return -1;
125 }
126
127 Q_DECLARE_METATYPE(QList<ApiTraceError>);
128
129 Retracer::Retracer(QObject *parent)
130     : QThread(parent),
131       m_benchmarking(false),
132       m_doubleBuffered(true),
133       m_captureState(false),
134       m_captureCall(0),
135       m_profileGpu(false),
136       m_profileCpu(false),
137       m_profilePixels(false)
138 {
139     qRegisterMetaType<QList<ApiTraceError> >();
140 }
141
142 QString Retracer::fileName() const
143 {
144     return m_fileName;
145 }
146
147 void Retracer::setFileName(const QString &name)
148 {
149     m_fileName = name;
150 }
151
152 void Retracer::setAPI(trace::API api)
153 {
154     m_api = api;
155 }
156
157 bool Retracer::isBenchmarking() const
158 {
159     return m_benchmarking;
160 }
161
162 void Retracer::setBenchmarking(bool bench)
163 {
164     m_benchmarking = bench;
165 }
166
167 bool Retracer::isDoubleBuffered() const
168 {
169     return m_doubleBuffered;
170 }
171
172 void Retracer::setDoubleBuffered(bool db)
173 {
174     m_doubleBuffered = db;
175 }
176
177 bool Retracer::isProfilingGpu() const
178 {
179     return m_profileGpu;
180 }
181
182 bool Retracer::isProfilingCpu() const
183 {
184     return m_profileCpu;
185 }
186
187 bool Retracer::isProfilingPixels() const
188 {
189     return m_profilePixels;
190 }
191
192 bool Retracer::isProfiling() const
193 {
194     return m_profileGpu || m_profileCpu || m_profilePixels;
195 }
196
197 void Retracer::setProfiling(bool gpu, bool cpu, bool pixels)
198 {
199     m_profileGpu = gpu;
200     m_profileCpu = cpu;
201     m_profilePixels = pixels;
202 }
203
204 void Retracer::setCaptureAtCallNumber(qlonglong num)
205 {
206     m_captureCall = num;
207 }
208
209 qlonglong Retracer::captureAtCallNumber() const
210 {
211     return m_captureCall;
212 }
213
214 bool Retracer::captureState() const
215 {
216     return m_captureState;
217 }
218
219 void Retracer::setCaptureState(bool enable)
220 {
221     m_captureState = enable;
222 }
223
224 bool Retracer::captureThumbnails() const
225 {
226     return m_captureThumbnails;
227 }
228
229 void Retracer::setCaptureThumbnails(bool enable)
230 {
231     m_captureThumbnails = enable;
232 }
233
234 /**
235  * Starting point for the retracing thread.
236  *
237  * Overrides QThread::run().
238  */
239 void Retracer::run()
240 {
241     QString msg = QLatin1String("Replay finished!");
242
243     /*
244      * Construct command line
245      */
246
247     QString prog;
248     QStringList arguments;
249
250     switch (m_api) {
251     case trace::API_GL:
252         prog = QLatin1String("glretrace");
253         break;
254     case trace::API_EGL:
255         prog = QLatin1String("eglretrace");
256         break;
257     case trace::API_DX:
258     case trace::API_D3D7:
259     case trace::API_D3D8:
260     case trace::API_D3D9:
261     case trace::API_DXGI:
262 #ifdef Q_OS_WIN
263         prog = QLatin1String("d3dretrace");
264 #else
265         prog = QLatin1String("wine");
266         arguments << QLatin1String("d3dretrace.exe");
267 #endif
268         break;
269     default:
270         emit finished(QLatin1String("Unsupported API"));
271         return;
272     }
273
274     if (m_captureState) {
275         arguments << QLatin1String("-D");
276         arguments << QString::number(m_captureCall);
277     } else if (m_captureThumbnails) {
278         arguments << QLatin1String("-s"); // emit snapshots
279         arguments << QLatin1String("-"); // emit to stdout
280     } else if (isProfiling()) {
281         if (m_profileGpu) {
282             arguments << QLatin1String("--pgpu");
283         }
284
285         if (m_profileCpu) {
286             arguments << QLatin1String("--pcpu");
287         }
288
289         if (m_profilePixels) {
290             arguments << QLatin1String("--ppd");
291         }
292     } else {
293         if (m_doubleBuffered) {
294             arguments << QLatin1String("--db");
295         } else {
296             arguments << QLatin1String("--sb");
297         }
298
299         if (m_benchmarking) {
300             arguments << QLatin1String("-b");
301         }
302     }
303
304     arguments << m_fileName;
305
306     /*
307      * Start the process.
308      */
309
310     QProcess process;
311
312     process.start(prog, arguments, QIODevice::ReadOnly);
313     if (!process.waitForStarted(-1)) {
314         emit finished(QLatin1String("Could not start process"));
315         return;
316     }
317
318     /*
319      * Process standard output
320      */
321
322     QList<QImage> thumbnails;
323     QVariantMap parsedJson;
324     trace::Profile* profile = NULL;
325
326     process.setReadChannel(QProcess::StandardOutput);
327     if (process.waitForReadyRead(-1)) {
328         BlockingIODevice io(&process);
329
330         if (m_captureState) {
331             /*
332              * Parse JSON from the output.
333              *
334              * XXX: QJSON's scanner is inneficient as it abuses single
335              * character QIODevice::peek (not cheap), instead of maintaining a
336              * lookahead character on its own.
337              */
338
339             bool ok = false;
340             QJson::Parser jsonParser;
341
342             // Allow Nan/Infinity
343             jsonParser.allowSpecialNumbers(true);
344 #if 0
345             parsedJson = jsonParser.parse(&io, &ok).toMap();
346 #else
347             /*
348              * XXX: QJSON expects blocking IO, and it looks like
349              * BlockingIODevice does not work reliably in all cases.
350              */
351             process.waitForFinished(-1);
352             parsedJson = jsonParser.parse(&process, &ok).toMap();
353 #endif
354             if (!ok) {
355                 msg = QLatin1String("failed to parse JSON");
356             }
357         } else if (m_captureThumbnails) {
358             /*
359              * Parse concatenated PNM images from output.
360              */
361
362             while (!io.atEnd()) {
363                 unsigned channels = 0;
364                 unsigned width = 0;
365                 unsigned height = 0;
366
367                 char header[512];
368                 qint64 headerSize = 0;
369                 int headerLines = 3; // assume no optional comment line
370
371                 for (int headerLine = 0; headerLine < headerLines; ++headerLine) {
372                     qint64 headerRead = io.readLine(&header[headerSize], sizeof(header) - headerSize);
373
374                     // if header actually contains optional comment line, ...
375                     if (headerLine == 1 && header[headerSize] == '#') {
376                         ++headerLines;
377                     }
378
379                     headerSize += headerRead;
380                 }
381
382                 const char *headerEnd = image::readPNMHeader(header, headerSize, &channels, &width, &height);
383
384                 // if invalid PNM header was encountered, ...
385                 if (header == headerEnd) {
386                     qDebug() << "error: invalid snapshot stream encountered";
387                     break;
388                 }
389
390                 // qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height";
391
392                 QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888);
393
394                 int rowBytes = channels * width;
395                 for (int y = 0; y < height; ++y) {
396                     unsigned char *scanLine = snapshot.scanLine(y);
397                     qint64 readBytes = io.read((char *) scanLine, rowBytes);
398                     Q_ASSERT(readBytes == rowBytes);
399                     (void)readBytes;
400                 }
401
402                 QImage thumb = thumbnail(snapshot);
403                 thumbnails.append(thumb);
404             }
405
406             Q_ASSERT(process.state() != QProcess::Running);
407         } else if (isProfiling()) {
408             profile = new trace::Profile();
409
410             while (!io.atEnd()) {
411                 char line[256];
412                 qint64 lineLength;
413
414                 lineLength = io.readLine(line, 256);
415
416                 if (lineLength == -1)
417                     break;
418
419                 trace::Profiler::parseLine(line, profile);
420             }
421         } else {
422             QByteArray output;
423             output = process.readAllStandardOutput();
424             if (output.length() < 80) {
425                 msg = QString::fromUtf8(output);
426             }
427         }
428     }
429
430     /*
431      * Wait for process termination
432      */
433
434     process.waitForFinished(-1);
435
436     if (process.exitStatus() != QProcess::NormalExit) {
437         msg = QLatin1String("Process crashed");
438     } else if (process.exitCode() != 0) {
439         msg = QLatin1String("Process exited with non zero exit code");
440     }
441
442     /*
443      * Parse errors.
444      */
445
446     QList<ApiTraceError> errors;
447     process.setReadChannel(QProcess::StandardError);
448     QRegExp regexp("(^\\d+): +(\\b\\w+\\b): ([^\\r\\n]+)[\\r\\n]*$");
449     while (!process.atEnd()) {
450         QString line = process.readLine();
451         if (regexp.indexIn(line) != -1) {
452             ApiTraceError error;
453             error.callIndex = regexp.cap(1).toInt();
454             error.type = regexp.cap(2);
455             error.message = regexp.cap(3);
456             errors.append(error);
457         } else if (!errors.isEmpty()) {
458             // Probably a multiligne message
459             ApiTraceError &previous = errors.last();
460             if (line.endsWith("\n")) {
461                 line.chop(1);
462             }
463             previous.message.append('\n');
464             previous.message.append(line);
465         }
466     }
467
468     /*
469      * Emit signals
470      */
471
472     if (m_captureState) {
473         ApiTraceState *state = new ApiTraceState(parsedJson);
474         emit foundState(state);
475         msg = QLatin1String("State fetched.");
476     }
477
478     if (m_captureThumbnails && !thumbnails.isEmpty()) {
479         emit foundThumbnails(thumbnails);
480     }
481
482     if (isProfiling() && profile) {
483         emit foundProfile(profile);
484     }
485
486     if (!errors.isEmpty()) {
487         emit retraceErrors(errors);
488     }
489
490     emit finished(msg);
491 }
492
493 #include "retracer.moc"