blob: f3f1feb8738b939f543556d685492a7b53e68fa6 [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),
Peter Lohrmannb34c6752013-07-10 11:08:14 -0400133 m_singlethread(false),
José Fonsecac03e4b02014-05-30 17:24:42 +0100134 m_useCoreProfile(false),
Zack Rusin3acde362011-04-06 01:11:55 -0400135 m_captureState(false),
José Fonsecac03e4b02014-05-30 17:24:42 +0100136 m_captureThumbnails(false),
James Bentonfc4f55a2012-08-08 17:09:07 +0100137 m_captureCall(0),
138 m_profileGpu(false),
139 m_profileCpu(false),
140 m_profilePixels(false)
Zack Rusin3acde362011-04-06 01:11:55 -0400141{
José Fonseca5bba4772012-03-25 12:46:04 +0100142 qRegisterMetaType<QList<ApiTraceError> >();
Zack Rusin3acde362011-04-06 01:11:55 -0400143}
144
145QString Retracer::fileName() const
146{
147 return m_fileName;
148}
149
150void Retracer::setFileName(const QString &name)
151{
152 m_fileName = name;
153}
154
Carl Worth7257dfc2012-08-09 08:21:42 -0700155QString Retracer::remoteTarget() const
156{
157 return m_remoteTarget;
158}
159
160void Retracer::setRemoteTarget(const QString &host)
161{
162 m_remoteTarget = host;
163}
164
José Fonseca62997b42011-11-27 15:16:34 +0000165void Retracer::setAPI(trace::API api)
166{
167 m_api = api;
168}
169
Zack Rusin3acde362011-04-06 01:11:55 -0400170bool Retracer::isBenchmarking() const
171{
172 return m_benchmarking;
173}
174
175void Retracer::setBenchmarking(bool bench)
176{
177 m_benchmarking = bench;
178}
179
180bool Retracer::isDoubleBuffered() const
181{
182 return m_doubleBuffered;
183}
184
185void Retracer::setDoubleBuffered(bool db)
186{
187 m_doubleBuffered = db;
188}
189
Peter Lohrmannb34c6752013-07-10 11:08:14 -0400190bool Retracer::isSinglethread() const
191{
192 return m_singlethread;
193}
194
195void Retracer::setSinglethread(bool singlethread)
196{
197 m_singlethread = singlethread;
198}
199
Corey Richardsonf3006462014-01-26 17:15:42 -0500200bool Retracer::isCoreProfile() const
201{
202 return m_useCoreProfile;
203}
204
205void Retracer::setCoreProfile(bool coreprofile)
206{
207 m_useCoreProfile = coreprofile;
208}
209
James Bentonfc4f55a2012-08-08 17:09:07 +0100210bool Retracer::isProfilingGpu() const
211{
212 return m_profileGpu;
213}
214
215bool Retracer::isProfilingCpu() const
216{
217 return m_profileCpu;
218}
219
220bool Retracer::isProfilingPixels() const
221{
222 return m_profilePixels;
223}
224
225bool Retracer::isProfiling() const
226{
227 return m_profileGpu || m_profileCpu || m_profilePixels;
228}
229
230void Retracer::setProfiling(bool gpu, bool cpu, bool pixels)
231{
232 m_profileGpu = gpu;
233 m_profileCpu = cpu;
234 m_profilePixels = pixels;
235}
236
Zack Rusin3acde362011-04-06 01:11:55 -0400237void Retracer::setCaptureAtCallNumber(qlonglong num)
238{
239 m_captureCall = num;
240}
241
242qlonglong Retracer::captureAtCallNumber() const
243{
244 return m_captureCall;
245}
246
247bool Retracer::captureState() const
248{
249 return m_captureState;
250}
251
252void Retracer::setCaptureState(bool enable)
253{
254 m_captureState = enable;
255}
256
Dan McCabe66dfdda2012-03-05 17:20:39 -0800257bool Retracer::captureThumbnails() const
258{
259 return m_captureThumbnails;
260}
261
262void Retracer::setCaptureThumbnails(bool enable)
263{
264 m_captureThumbnails = enable;
265}
266
José Fonseca5bba4772012-03-25 12:46:04 +0100267/**
268 * Starting point for the retracing thread.
269 *
270 * Overrides QThread::run().
271 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400272void Retracer::run()
273{
José Fonseca126f64b2012-03-28 00:13:55 +0100274 QString msg = QLatin1String("Replay finished!");
Zack Rusinf389ae82011-04-10 19:27:28 -0400275
José Fonseca5bba4772012-03-25 12:46:04 +0100276 /*
277 * Construct command line
278 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400279
José Fonseca62997b42011-11-27 15:16:34 +0000280 QString prog;
Zack Rusinf389ae82011-04-10 19:27:28 -0400281 QStringList arguments;
Zack Rusin16ae0362011-04-11 21:30:04 -0400282
José Fonseca889d32c2012-04-23 10:18:28 +0100283 switch (m_api) {
284 case trace::API_GL:
José Fonseca62997b42011-11-27 15:16:34 +0000285 prog = QLatin1String("glretrace");
José Fonseca889d32c2012-04-23 10:18:28 +0100286 break;
287 case trace::API_EGL:
José Fonseca62997b42011-11-27 15:16:34 +0000288 prog = QLatin1String("eglretrace");
José Fonseca889d32c2012-04-23 10:18:28 +0100289 break;
290 case trace::API_DX:
291 case trace::API_D3D7:
292 case trace::API_D3D8:
293 case trace::API_D3D9:
José Fonsecae51e22f2012-12-07 07:48:10 +0000294 case trace::API_DXGI:
José Fonseca889d32c2012-04-23 10:18:28 +0100295#ifdef Q_OS_WIN
296 prog = QLatin1String("d3dretrace");
297#else
298 prog = QLatin1String("wine");
299 arguments << QLatin1String("d3dretrace.exe");
300#endif
301 break;
302 default:
José Fonseca67964382012-03-27 23:54:30 +0100303 emit finished(QLatin1String("Unsupported API"));
José Fonseca62997b42011-11-27 15:16:34 +0000304 return;
305 }
306
Peter Lohrmannb34c6752013-07-10 11:08:14 -0400307 if (m_singlethread) {
308 arguments << QLatin1String("--singlethread");
309 }
310
Corey Richardsonf3006462014-01-26 17:15:42 -0500311 if (m_useCoreProfile) {
312 arguments << QLatin1String("--core");
313 }
314
José Fonseca5bba4772012-03-25 12:46:04 +0100315 if (m_captureState) {
316 arguments << QLatin1String("-D");
317 arguments << QString::number(m_captureCall);
318 } else if (m_captureThumbnails) {
319 arguments << QLatin1String("-s"); // emit snapshots
320 arguments << QLatin1String("-"); // emit to stdout
James Bentonfc4f55a2012-08-08 17:09:07 +0100321 } else if (isProfiling()) {
322 if (m_profileGpu) {
José Fonsecabce31f62012-11-14 07:21:01 +0000323 arguments << QLatin1String("--pgpu");
James Bentonfc4f55a2012-08-08 17:09:07 +0100324 }
325
326 if (m_profileCpu) {
José Fonsecabce31f62012-11-14 07:21:01 +0000327 arguments << QLatin1String("--pcpu");
James Bentonfc4f55a2012-08-08 17:09:07 +0100328 }
329
330 if (m_profilePixels) {
José Fonsecabce31f62012-11-14 07:21:01 +0000331 arguments << QLatin1String("--ppd");
James Bentonfc4f55a2012-08-08 17:09:07 +0100332 }
333 } else {
334 if (m_doubleBuffered) {
José Fonsecabce31f62012-11-14 07:21:01 +0000335 arguments << QLatin1String("--db");
James Bentonfc4f55a2012-08-08 17:09:07 +0100336 } else {
José Fonsecabce31f62012-11-14 07:21:01 +0000337 arguments << QLatin1String("--sb");
James Bentonfc4f55a2012-08-08 17:09:07 +0100338 }
339
340 if (m_benchmarking) {
341 arguments << QLatin1String("-b");
342 }
Zack Rusinf389ae82011-04-10 19:27:28 -0400343 }
344
345 arguments << m_fileName;
346
José Fonseca5bba4772012-03-25 12:46:04 +0100347 /*
Carl Worth7257dfc2012-08-09 08:21:42 -0700348 * Support remote execution on a separate target.
349 */
350
351 if (m_remoteTarget.length() != 0) {
352 arguments.prepend(prog);
353 arguments.prepend(m_remoteTarget);
354 prog = QLatin1String("ssh");
355 }
356
357 /*
José Fonseca5bba4772012-03-25 12:46:04 +0100358 * Start the process.
359 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400360
José Fonseca5bba4772012-03-25 12:46:04 +0100361 QProcess process;
Zack Rusinf389ae82011-04-10 19:27:28 -0400362
José Fonseca6ea8dee2012-03-25 17:25:24 +0100363 process.start(prog, arguments, QIODevice::ReadOnly);
José Fonseca5bba4772012-03-25 12:46:04 +0100364 if (!process.waitForStarted(-1)) {
365 emit finished(QLatin1String("Could not start process"));
366 return;
367 }
José Fonseca56cd8ac2011-11-24 16:30:49 +0000368
José Fonseca5bba4772012-03-25 12:46:04 +0100369 /*
370 * Process standard output
371 */
372
373 QList<QImage> thumbnails;
374 QVariantMap parsedJson;
James Bentonfc4f55a2012-08-08 17:09:07 +0100375 trace::Profile* profile = NULL;
José Fonseca5bba4772012-03-25 12:46:04 +0100376
377 process.setReadChannel(QProcess::StandardOutput);
378 if (process.waitForReadyRead(-1)) {
José Fonseca6ea8dee2012-03-25 17:25:24 +0100379 BlockingIODevice io(&process);
380
José Fonseca5bba4772012-03-25 12:46:04 +0100381 if (m_captureState) {
382 /*
383 * Parse JSON from the output.
384 *
José Fonseca5bba4772012-03-25 12:46:04 +0100385 * XXX: QJSON's scanner is inneficient as it abuses single
386 * character QIODevice::peek (not cheap), instead of maintaining a
387 * lookahead character on its own.
388 */
389
José Fonseca5bba4772012-03-25 12:46:04 +0100390 bool ok = false;
391 QJson::Parser jsonParser;
José Fonseca74936bb2012-10-27 10:13:58 +0100392
393 // Allow Nan/Infinity
394 jsonParser.allowSpecialNumbers(true);
José Fonseca95b40562012-04-05 20:06:42 +0100395#if 0
José Fonseca6ea8dee2012-03-25 17:25:24 +0100396 parsedJson = jsonParser.parse(&io, &ok).toMap();
José Fonseca95b40562012-04-05 20:06:42 +0100397#else
398 /*
399 * XXX: QJSON expects blocking IO, and it looks like
400 * BlockingIODevice does not work reliably in all cases.
401 */
402 process.waitForFinished(-1);
403 parsedJson = jsonParser.parse(&process, &ok).toMap();
404#endif
José Fonseca5bba4772012-03-25 12:46:04 +0100405 if (!ok) {
406 msg = QLatin1String("failed to parse JSON");
407 }
408 } else if (m_captureThumbnails) {
409 /*
410 * Parse concatenated PNM images from output.
411 */
Dan McCabe66dfdda2012-03-05 17:20:39 -0800412
José Fonseca6ea8dee2012-03-25 17:25:24 +0100413 while (!io.atEnd()) {
José Fonsecabeda4442013-09-12 17:25:04 +0100414 image::PNMInfo info;
José Fonseca5bba4772012-03-25 12:46:04 +0100415
416 char header[512];
417 qint64 headerSize = 0;
418 int headerLines = 3; // assume no optional comment line
419
420 for (int headerLine = 0; headerLine < headerLines; ++headerLine) {
José Fonseca6ea8dee2012-03-25 17:25:24 +0100421 qint64 headerRead = io.readLine(&header[headerSize], sizeof(header) - headerSize);
José Fonseca5bba4772012-03-25 12:46:04 +0100422
423 // if header actually contains optional comment line, ...
424 if (headerLine == 1 && header[headerSize] == '#') {
425 ++headerLines;
426 }
427
428 headerSize += headerRead;
429 }
430
José Fonsecabeda4442013-09-12 17:25:04 +0100431 const char *headerEnd = image::readPNMHeader(header, headerSize, info);
José Fonseca5bba4772012-03-25 12:46:04 +0100432
433 // if invalid PNM header was encountered, ...
José Fonsecabeda4442013-09-12 17:25:04 +0100434 if (headerEnd == NULL ||
435 info.channelType != image::TYPE_UNORM8) {
José Fonseca5bba4772012-03-25 12:46:04 +0100436 qDebug() << "error: invalid snapshot stream encountered";
437 break;
438 }
439
José Fonsecabeda4442013-09-12 17:25:04 +0100440 unsigned channels = info.channels;
441 unsigned width = info.width;
442 unsigned height = info.height;
443
José Fonseca5bba4772012-03-25 12:46:04 +0100444 // qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height";
445
446 QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888);
447
448 int rowBytes = channels * width;
449 for (int y = 0; y < height; ++y) {
450 unsigned char *scanLine = snapshot.scanLine(y);
José Fonseca6ea8dee2012-03-25 17:25:24 +0100451 qint64 readBytes = io.read((char *) scanLine, rowBytes);
452 Q_ASSERT(readBytes == rowBytes);
José Fonsecaa2bf2872012-11-15 13:35:22 +0000453 (void)readBytes;
José Fonseca5bba4772012-03-25 12:46:04 +0100454 }
455
José Fonsecadc9e9c62012-03-26 10:29:32 +0100456 QImage thumb = thumbnail(snapshot);
457 thumbnails.append(thumb);
Dan McCabe66dfdda2012-03-05 17:20:39 -0800458 }
José Fonseca5bba4772012-03-25 12:46:04 +0100459
460 Q_ASSERT(process.state() != QProcess::Running);
James Bentonfc4f55a2012-08-08 17:09:07 +0100461 } else if (isProfiling()) {
462 profile = new trace::Profile();
José Fonseca5bba4772012-03-25 12:46:04 +0100463
James Bentonfc4f55a2012-08-08 17:09:07 +0100464 while (!io.atEnd()) {
465 char line[256];
466 qint64 lineLength;
467
468 lineLength = io.readLine(line, 256);
469
470 if (lineLength == -1)
471 break;
472
473 trace::Profiler::parseLine(line, profile);
474 }
José Fonseca56cd8ac2011-11-24 16:30:49 +0000475 } else {
José Fonseca676cd172012-03-24 09:46:24 +0000476 QByteArray output;
José Fonseca5bba4772012-03-25 12:46:04 +0100477 output = process.readAllStandardOutput();
José Fonseca126f64b2012-03-28 00:13:55 +0100478 if (output.length() < 80) {
479 msg = QString::fromUtf8(output);
480 }
José Fonseca56cd8ac2011-11-24 16:30:49 +0000481 }
Zack Rusinf389ae82011-04-10 19:27:28 -0400482 }
483
José Fonseca5bba4772012-03-25 12:46:04 +0100484 /*
485 * Wait for process termination
486 */
487
488 process.waitForFinished(-1);
489
490 if (process.exitStatus() != QProcess::NormalExit) {
491 msg = QLatin1String("Process crashed");
492 } else if (process.exitCode() != 0) {
493 msg = QLatin1String("Process exited with non zero exit code");
494 }
495
496 /*
497 * Parse errors.
498 */
499
Zack Rusin10fd4772011-09-14 01:45:12 -0400500 QList<ApiTraceError> errors;
José Fonseca5bba4772012-03-25 12:46:04 +0100501 process.setReadChannel(QProcess::StandardError);
José Fonseca8fdf56c2012-03-24 10:06:56 +0000502 QRegExp regexp("(^\\d+): +(\\b\\w+\\b): ([^\\r\\n]+)[\\r\\n]*$");
José Fonseca5bba4772012-03-25 12:46:04 +0100503 while (!process.atEnd()) {
504 QString line = process.readLine();
Zack Rusinb39e1c62011-04-19 23:09:26 -0400505 if (regexp.indexIn(line) != -1) {
Zack Rusin10fd4772011-09-14 01:45:12 -0400506 ApiTraceError error;
Zack Rusinb39e1c62011-04-19 23:09:26 -0400507 error.callIndex = regexp.cap(1).toInt();
508 error.type = regexp.cap(2);
509 error.message = regexp.cap(3);
510 errors.append(error);
gregoryf2329b62012-07-06 21:48:59 +0200511 } else if (!errors.isEmpty()) {
512 // Probably a multiligne message
513 ApiTraceError &previous = errors.last();
514 if (line.endsWith("\n")) {
515 line.chop(1);
516 }
517 previous.message.append('\n');
518 previous.message.append(line);
Zack Rusinb39e1c62011-04-19 23:09:26 -0400519 }
520 }
José Fonseca5bba4772012-03-25 12:46:04 +0100521
522 /*
523 * Emit signals
524 */
525
526 if (m_captureState) {
527 ApiTraceState *state = new ApiTraceState(parsedJson);
528 emit foundState(state);
José Fonseca5bba4772012-03-25 12:46:04 +0100529 }
530
531 if (m_captureThumbnails && !thumbnails.isEmpty()) {
532 emit foundThumbnails(thumbnails);
533 }
534
James Bentonfc4f55a2012-08-08 17:09:07 +0100535 if (isProfiling() && profile) {
536 emit foundProfile(profile);
537 }
538
Zack Rusinb39e1c62011-04-19 23:09:26 -0400539 if (!errors.isEmpty()) {
540 emit retraceErrors(errors);
541 }
José Fonseca5bba4772012-03-25 12:46:04 +0100542
Zack Rusinf389ae82011-04-10 19:27:28 -0400543 emit finished(msg);
Zack Rusin3acde362011-04-06 01:11:55 -0400544}
545
Zack Rusin3acde362011-04-06 01:11:55 -0400546#include "retracer.moc"