blob: 2de9d23ee849de85f12c6cbf51461750ba7edf68 [file] [log] [blame]
Zack Rusin3acde362011-04-06 01:11:55 -04001#include "retracer.h"
2
Zack Rusinf389ae82011-04-10 19:27:28 -04003#include "apitracecall.h"
José Fonseca3f456402012-03-25 20:59:24 +01004#include "thumbnail.h"
Zack Rusinf389ae82011-04-10 19:27:28 -04005
José Fonsecae7102bf2012-12-07 07:33:05 +00006#include "image/image.hpp"
Dan McCabe66dfdda2012-03-05 17:20:39 -08007
James Bentonfc4f55a2012-08-08 17:09:07 +01008#include "trace_profiler.hpp"
9
Zack Rusin3acde362011-04-06 01:11:55 -040010#include <QDebug>
Zack Rusinf389ae82011-04-10 19:27:28 -040011#include <QVariant>
Dan McCabe66dfdda2012-03-05 17:20:39 -080012#include <QList>
13#include <QImage>
Zack Rusinf389ae82011-04-10 19:27:28 -040014
15#include <qjson/parser.h>
Zack Rusin3acde362011-04-06 01:11:55 -040016
José Fonseca6ea8dee2012-03-25 17:25:24 +010017/**
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 */
35class 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. */
40public:
41 BlockingIODevice(QProcess * io);
42 bool isSequential() const;
43 bool atEnd() const;
44 bool waitForReadyRead(int msecs = -1);
45
46protected:
47 qint64 readData(char * data, qint64 maxSize);
48 qint64 writeData(const char * data, qint64 maxSize);
49
50private:
51 QProcess *m_device;
52};
53
54BlockingIODevice::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
67bool BlockingIODevice::isSequential() const
68{
69 return true;
70}
71
72bool 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
88bool BlockingIODevice::waitForReadyRead(int msecs)
89{
90 Q_UNUSED(msecs);
91 return true;
92}
93
94qint64 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
121qint64 BlockingIODevice::writeData(const char * data, qint64 maxSize)
122{
123 Q_ASSERT(false);
124 return -1;
125}
126
José Fonseca5bba4772012-03-25 12:46:04 +0100127Q_DECLARE_METATYPE(QList<ApiTraceError>);
128
Zack Rusin3acde362011-04-06 01:11:55 -0400129Retracer::Retracer(QObject *parent)
Zack Rusinf389ae82011-04-10 19:27:28 -0400130 : QThread(parent),
Zack Rusin404a1ef2011-04-19 23:49:56 -0400131 m_benchmarking(false),
Zack Rusin3acde362011-04-06 01:11:55 -0400132 m_doubleBuffered(true),
133 m_captureState(false),
James Bentonfc4f55a2012-08-08 17:09:07 +0100134 m_captureCall(0),
135 m_profileGpu(false),
136 m_profileCpu(false),
137 m_profilePixels(false)
Zack Rusin3acde362011-04-06 01:11:55 -0400138{
José Fonseca5bba4772012-03-25 12:46:04 +0100139 qRegisterMetaType<QList<ApiTraceError> >();
Zack Rusin3acde362011-04-06 01:11:55 -0400140}
141
142QString Retracer::fileName() const
143{
144 return m_fileName;
145}
146
147void Retracer::setFileName(const QString &name)
148{
149 m_fileName = name;
150}
151
Carl Worth7257dfc2012-08-09 08:21:42 -0700152QString Retracer::remoteTarget() const
153{
154 return m_remoteTarget;
155}
156
157void Retracer::setRemoteTarget(const QString &host)
158{
159 m_remoteTarget = host;
160}
161
José Fonseca62997b42011-11-27 15:16:34 +0000162void Retracer::setAPI(trace::API api)
163{
164 m_api = api;
165}
166
Zack Rusin3acde362011-04-06 01:11:55 -0400167bool Retracer::isBenchmarking() const
168{
169 return m_benchmarking;
170}
171
172void Retracer::setBenchmarking(bool bench)
173{
174 m_benchmarking = bench;
175}
176
177bool Retracer::isDoubleBuffered() const
178{
179 return m_doubleBuffered;
180}
181
182void Retracer::setDoubleBuffered(bool db)
183{
184 m_doubleBuffered = db;
185}
186
James Bentonfc4f55a2012-08-08 17:09:07 +0100187bool Retracer::isProfilingGpu() const
188{
189 return m_profileGpu;
190}
191
192bool Retracer::isProfilingCpu() const
193{
194 return m_profileCpu;
195}
196
197bool Retracer::isProfilingPixels() const
198{
199 return m_profilePixels;
200}
201
202bool Retracer::isProfiling() const
203{
204 return m_profileGpu || m_profileCpu || m_profilePixels;
205}
206
207void Retracer::setProfiling(bool gpu, bool cpu, bool pixels)
208{
209 m_profileGpu = gpu;
210 m_profileCpu = cpu;
211 m_profilePixels = pixels;
212}
213
Zack Rusin3acde362011-04-06 01:11:55 -0400214void Retracer::setCaptureAtCallNumber(qlonglong num)
215{
216 m_captureCall = num;
217}
218
219qlonglong Retracer::captureAtCallNumber() const
220{
221 return m_captureCall;
222}
223
224bool Retracer::captureState() const
225{
226 return m_captureState;
227}
228
229void Retracer::setCaptureState(bool enable)
230{
231 m_captureState = enable;
232}
233
Dan McCabe66dfdda2012-03-05 17:20:39 -0800234bool Retracer::captureThumbnails() const
235{
236 return m_captureThumbnails;
237}
238
239void Retracer::setCaptureThumbnails(bool enable)
240{
241 m_captureThumbnails = enable;
242}
243
José Fonseca5bba4772012-03-25 12:46:04 +0100244/**
245 * Starting point for the retracing thread.
246 *
247 * Overrides QThread::run().
248 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400249void Retracer::run()
250{
José Fonseca126f64b2012-03-28 00:13:55 +0100251 QString msg = QLatin1String("Replay finished!");
Zack Rusinf389ae82011-04-10 19:27:28 -0400252
José Fonseca5bba4772012-03-25 12:46:04 +0100253 /*
254 * Construct command line
255 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400256
José Fonseca62997b42011-11-27 15:16:34 +0000257 QString prog;
Zack Rusinf389ae82011-04-10 19:27:28 -0400258 QStringList arguments;
Zack Rusin16ae0362011-04-11 21:30:04 -0400259
José Fonseca889d32c2012-04-23 10:18:28 +0100260 switch (m_api) {
261 case trace::API_GL:
José Fonseca62997b42011-11-27 15:16:34 +0000262 prog = QLatin1String("glretrace");
José Fonseca889d32c2012-04-23 10:18:28 +0100263 break;
264 case trace::API_EGL:
José Fonseca62997b42011-11-27 15:16:34 +0000265 prog = QLatin1String("eglretrace");
José Fonseca889d32c2012-04-23 10:18:28 +0100266 break;
267 case trace::API_DX:
268 case trace::API_D3D7:
269 case trace::API_D3D8:
270 case trace::API_D3D9:
José Fonsecae51e22f2012-12-07 07:48:10 +0000271 case trace::API_DXGI:
José Fonseca889d32c2012-04-23 10:18:28 +0100272#ifdef Q_OS_WIN
273 prog = QLatin1String("d3dretrace");
274#else
275 prog = QLatin1String("wine");
276 arguments << QLatin1String("d3dretrace.exe");
277#endif
278 break;
279 default:
José Fonseca67964382012-03-27 23:54:30 +0100280 emit finished(QLatin1String("Unsupported API"));
José Fonseca62997b42011-11-27 15:16:34 +0000281 return;
282 }
283
José Fonseca5bba4772012-03-25 12:46:04 +0100284 if (m_captureState) {
285 arguments << QLatin1String("-D");
286 arguments << QString::number(m_captureCall);
287 } else if (m_captureThumbnails) {
288 arguments << QLatin1String("-s"); // emit snapshots
289 arguments << QLatin1String("-"); // emit to stdout
James Bentonfc4f55a2012-08-08 17:09:07 +0100290 } else if (isProfiling()) {
291 if (m_profileGpu) {
José Fonsecabce31f62012-11-14 07:21:01 +0000292 arguments << QLatin1String("--pgpu");
James Bentonfc4f55a2012-08-08 17:09:07 +0100293 }
294
295 if (m_profileCpu) {
José Fonsecabce31f62012-11-14 07:21:01 +0000296 arguments << QLatin1String("--pcpu");
James Bentonfc4f55a2012-08-08 17:09:07 +0100297 }
298
299 if (m_profilePixels) {
José Fonsecabce31f62012-11-14 07:21:01 +0000300 arguments << QLatin1String("--ppd");
James Bentonfc4f55a2012-08-08 17:09:07 +0100301 }
302 } else {
303 if (m_doubleBuffered) {
José Fonsecabce31f62012-11-14 07:21:01 +0000304 arguments << QLatin1String("--db");
James Bentonfc4f55a2012-08-08 17:09:07 +0100305 } else {
José Fonsecabce31f62012-11-14 07:21:01 +0000306 arguments << QLatin1String("--sb");
James Bentonfc4f55a2012-08-08 17:09:07 +0100307 }
308
309 if (m_benchmarking) {
310 arguments << QLatin1String("-b");
311 }
Zack Rusinf389ae82011-04-10 19:27:28 -0400312 }
313
314 arguments << m_fileName;
315
José Fonseca5bba4772012-03-25 12:46:04 +0100316 /*
Carl Worth7257dfc2012-08-09 08:21:42 -0700317 * Support remote execution on a separate target.
318 */
319
320 if (m_remoteTarget.length() != 0) {
321 arguments.prepend(prog);
322 arguments.prepend(m_remoteTarget);
323 prog = QLatin1String("ssh");
324 }
325
326 /*
José Fonseca5bba4772012-03-25 12:46:04 +0100327 * Start the process.
328 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400329
José Fonseca5bba4772012-03-25 12:46:04 +0100330 QProcess process;
Zack Rusinf389ae82011-04-10 19:27:28 -0400331
José Fonseca6ea8dee2012-03-25 17:25:24 +0100332 process.start(prog, arguments, QIODevice::ReadOnly);
José Fonseca5bba4772012-03-25 12:46:04 +0100333 if (!process.waitForStarted(-1)) {
334 emit finished(QLatin1String("Could not start process"));
335 return;
336 }
José Fonseca56cd8ac2011-11-24 16:30:49 +0000337
José Fonseca5bba4772012-03-25 12:46:04 +0100338 /*
339 * Process standard output
340 */
341
342 QList<QImage> thumbnails;
343 QVariantMap parsedJson;
James Bentonfc4f55a2012-08-08 17:09:07 +0100344 trace::Profile* profile = NULL;
José Fonseca5bba4772012-03-25 12:46:04 +0100345
346 process.setReadChannel(QProcess::StandardOutput);
347 if (process.waitForReadyRead(-1)) {
José Fonseca6ea8dee2012-03-25 17:25:24 +0100348 BlockingIODevice io(&process);
349
José Fonseca5bba4772012-03-25 12:46:04 +0100350 if (m_captureState) {
351 /*
352 * Parse JSON from the output.
353 *
José Fonseca5bba4772012-03-25 12:46:04 +0100354 * XXX: QJSON's scanner is inneficient as it abuses single
355 * character QIODevice::peek (not cheap), instead of maintaining a
356 * lookahead character on its own.
357 */
358
José Fonseca5bba4772012-03-25 12:46:04 +0100359 bool ok = false;
360 QJson::Parser jsonParser;
José Fonseca74936bb2012-10-27 10:13:58 +0100361
362 // Allow Nan/Infinity
363 jsonParser.allowSpecialNumbers(true);
José Fonseca95b40562012-04-05 20:06:42 +0100364#if 0
José Fonseca6ea8dee2012-03-25 17:25:24 +0100365 parsedJson = jsonParser.parse(&io, &ok).toMap();
José Fonseca95b40562012-04-05 20:06:42 +0100366#else
367 /*
368 * XXX: QJSON expects blocking IO, and it looks like
369 * BlockingIODevice does not work reliably in all cases.
370 */
371 process.waitForFinished(-1);
372 parsedJson = jsonParser.parse(&process, &ok).toMap();
373#endif
José Fonseca5bba4772012-03-25 12:46:04 +0100374 if (!ok) {
375 msg = QLatin1String("failed to parse JSON");
376 }
377 } else if (m_captureThumbnails) {
378 /*
379 * Parse concatenated PNM images from output.
380 */
Dan McCabe66dfdda2012-03-05 17:20:39 -0800381
José Fonseca6ea8dee2012-03-25 17:25:24 +0100382 while (!io.atEnd()) {
José Fonseca5bba4772012-03-25 12:46:04 +0100383 unsigned channels = 0;
384 unsigned width = 0;
385 unsigned height = 0;
386
387 char header[512];
388 qint64 headerSize = 0;
389 int headerLines = 3; // assume no optional comment line
390
391 for (int headerLine = 0; headerLine < headerLines; ++headerLine) {
José Fonseca6ea8dee2012-03-25 17:25:24 +0100392 qint64 headerRead = io.readLine(&header[headerSize], sizeof(header) - headerSize);
José Fonseca5bba4772012-03-25 12:46:04 +0100393
394 // if header actually contains optional comment line, ...
395 if (headerLine == 1 && header[headerSize] == '#') {
396 ++headerLines;
397 }
398
399 headerSize += headerRead;
400 }
401
402 const char *headerEnd = image::readPNMHeader(header, headerSize, &channels, &width, &height);
403
404 // if invalid PNM header was encountered, ...
405 if (header == headerEnd) {
406 qDebug() << "error: invalid snapshot stream encountered";
407 break;
408 }
409
410 // qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height";
411
412 QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888);
413
414 int rowBytes = channels * width;
415 for (int y = 0; y < height; ++y) {
416 unsigned char *scanLine = snapshot.scanLine(y);
José Fonseca6ea8dee2012-03-25 17:25:24 +0100417 qint64 readBytes = io.read((char *) scanLine, rowBytes);
418 Q_ASSERT(readBytes == rowBytes);
José Fonsecaa2bf2872012-11-15 13:35:22 +0000419 (void)readBytes;
José Fonseca5bba4772012-03-25 12:46:04 +0100420 }
421
José Fonsecadc9e9c62012-03-26 10:29:32 +0100422 QImage thumb = thumbnail(snapshot);
423 thumbnails.append(thumb);
Dan McCabe66dfdda2012-03-05 17:20:39 -0800424 }
José Fonseca5bba4772012-03-25 12:46:04 +0100425
426 Q_ASSERT(process.state() != QProcess::Running);
James Bentonfc4f55a2012-08-08 17:09:07 +0100427 } else if (isProfiling()) {
428 profile = new trace::Profile();
José Fonseca5bba4772012-03-25 12:46:04 +0100429
James Bentonfc4f55a2012-08-08 17:09:07 +0100430 while (!io.atEnd()) {
431 char line[256];
432 qint64 lineLength;
433
434 lineLength = io.readLine(line, 256);
435
436 if (lineLength == -1)
437 break;
438
439 trace::Profiler::parseLine(line, profile);
440 }
José Fonseca56cd8ac2011-11-24 16:30:49 +0000441 } else {
José Fonseca676cd172012-03-24 09:46:24 +0000442 QByteArray output;
José Fonseca5bba4772012-03-25 12:46:04 +0100443 output = process.readAllStandardOutput();
José Fonseca126f64b2012-03-28 00:13:55 +0100444 if (output.length() < 80) {
445 msg = QString::fromUtf8(output);
446 }
José Fonseca56cd8ac2011-11-24 16:30:49 +0000447 }
Zack Rusinf389ae82011-04-10 19:27:28 -0400448 }
449
José Fonseca5bba4772012-03-25 12:46:04 +0100450 /*
451 * Wait for process termination
452 */
453
454 process.waitForFinished(-1);
455
456 if (process.exitStatus() != QProcess::NormalExit) {
457 msg = QLatin1String("Process crashed");
458 } else if (process.exitCode() != 0) {
459 msg = QLatin1String("Process exited with non zero exit code");
460 }
461
462 /*
463 * Parse errors.
464 */
465
Zack Rusin10fd4772011-09-14 01:45:12 -0400466 QList<ApiTraceError> errors;
José Fonseca5bba4772012-03-25 12:46:04 +0100467 process.setReadChannel(QProcess::StandardError);
José Fonseca8fdf56c2012-03-24 10:06:56 +0000468 QRegExp regexp("(^\\d+): +(\\b\\w+\\b): ([^\\r\\n]+)[\\r\\n]*$");
José Fonseca5bba4772012-03-25 12:46:04 +0100469 while (!process.atEnd()) {
470 QString line = process.readLine();
Zack Rusinb39e1c62011-04-19 23:09:26 -0400471 if (regexp.indexIn(line) != -1) {
Zack Rusin10fd4772011-09-14 01:45:12 -0400472 ApiTraceError error;
Zack Rusinb39e1c62011-04-19 23:09:26 -0400473 error.callIndex = regexp.cap(1).toInt();
474 error.type = regexp.cap(2);
475 error.message = regexp.cap(3);
476 errors.append(error);
gregoryf2329b62012-07-06 21:48:59 +0200477 } else if (!errors.isEmpty()) {
478 // Probably a multiligne message
479 ApiTraceError &previous = errors.last();
480 if (line.endsWith("\n")) {
481 line.chop(1);
482 }
483 previous.message.append('\n');
484 previous.message.append(line);
Zack Rusinb39e1c62011-04-19 23:09:26 -0400485 }
486 }
José Fonseca5bba4772012-03-25 12:46:04 +0100487
488 /*
489 * Emit signals
490 */
491
492 if (m_captureState) {
493 ApiTraceState *state = new ApiTraceState(parsedJson);
494 emit foundState(state);
José Fonseca5bba4772012-03-25 12:46:04 +0100495 }
496
497 if (m_captureThumbnails && !thumbnails.isEmpty()) {
498 emit foundThumbnails(thumbnails);
499 }
500
James Bentonfc4f55a2012-08-08 17:09:07 +0100501 if (isProfiling() && profile) {
502 emit foundProfile(profile);
503 }
504
Zack Rusinb39e1c62011-04-19 23:09:26 -0400505 if (!errors.isEmpty()) {
506 emit retraceErrors(errors);
507 }
José Fonseca5bba4772012-03-25 12:46:04 +0100508
Zack Rusinf389ae82011-04-10 19:27:28 -0400509 emit finished(msg);
Zack Rusin3acde362011-04-06 01:11:55 -0400510}
511
Zack Rusin3acde362011-04-06 01:11:55 -0400512#include "retracer.moc"