blob: 02f01454959c0d2771bddf492d71e44454dd1220 [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>
Jose Fonsecac296a3e2015-05-01 17:43:54 +010014
15#include "qubjson.h"
16
Zack Rusin3acde362011-04-06 01:11:55 -040017
José Fonseca6ea8dee2012-03-25 17:25:24 +010018/**
19 * Wrapper around a QProcess which enforces IO to block .
20 *
21 * Several QIODevice users (notably QJSON) expect blocking semantics, e.g.,
22 * they expect that QIODevice::read() will blocked until the requested ammount
23 * of bytes is read or end of file is reached. But by default QProcess, does
24 * not block. And passing QIODevice::Unbuffered mitigates but does not fully
25 * address the problem either.
26 *
27 * This class wraps around QProcess, providing QIODevice interface, while
28 * ensuring that all reads block.
29 *
30 * This class also works around a bug in QProcess::atEnd() implementation.
31 *
32 * See also:
33 * - http://qt-project.org/wiki/Simple_Crypt_IO_Device
34 * - http://qt-project.org/wiki/Custom_IO_Device
35 */
36class BlockingIODevice : public QIODevice
37{
38 /* We don't use the Q_OBJECT in this class given we don't declare any
39 * signals and slots or use any other services provided by Qt's meta-object
40 * system. */
41public:
42 BlockingIODevice(QProcess * io);
43 bool isSequential() const;
44 bool atEnd() const;
45 bool waitForReadyRead(int msecs = -1);
46
47protected:
48 qint64 readData(char * data, qint64 maxSize);
49 qint64 writeData(const char * data, qint64 maxSize);
50
51private:
52 QProcess *m_device;
53};
54
55BlockingIODevice::BlockingIODevice(QProcess * io) :
56 m_device(io)
57{
58 /*
59 * We pass QIODevice::Unbuffered to prevent the base QIODevice class to do
60 * its own buffering on top of the overridden readData() method.
61 *
62 * The only buffering used will be to satisfy QIODevice::peek() and
63 * QIODevice::ungetChar().
64 */
65 setOpenMode(ReadOnly | Unbuffered);
66}
67
68bool BlockingIODevice::isSequential() const
69{
70 return true;
71}
72
73bool BlockingIODevice::atEnd() const
74{
75 /*
76 * XXX: QProcess::atEnd() documentation is wrong -- it will return true
77 * even when the process is running --, so we try to workaround that here.
78 */
79 if (m_device->atEnd()) {
80 if (m_device->state() == QProcess::Running) {
81 if (!m_device->waitForReadyRead(-1)) {
82 return true;
83 }
84 }
85 }
86 return false;
87}
88
89bool BlockingIODevice::waitForReadyRead(int msecs)
90{
91 Q_UNUSED(msecs);
92 return true;
93}
94
95qint64 BlockingIODevice::readData(char * data, qint64 maxSize)
96{
97 qint64 bytesToRead = maxSize;
98 qint64 readSoFar = 0;
99 do {
100 qint64 chunkSize = m_device->read(data + readSoFar, bytesToRead);
101 if (chunkSize < 0) {
102 if (readSoFar) {
103 return readSoFar;
104 } else {
105 return chunkSize;
106 }
107 }
108 Q_ASSERT(chunkSize <= bytesToRead);
109 bytesToRead -= chunkSize;
110 readSoFar += chunkSize;
111 if (bytesToRead) {
112 if (!m_device->waitForReadyRead(-1)) {
113 qDebug() << "waitForReadyRead failed\n";
114 break;
115 }
116 }
117 } while(bytesToRead);
118
119 return readSoFar;
120}
121
122qint64 BlockingIODevice::writeData(const char * data, qint64 maxSize)
123{
124 Q_ASSERT(false);
125 return -1;
126}
127
José Fonseca5bba4772012-03-25 12:46:04 +0100128Q_DECLARE_METATYPE(QList<ApiTraceError>);
129
Zack Rusin3acde362011-04-06 01:11:55 -0400130Retracer::Retracer(QObject *parent)
Zack Rusinf389ae82011-04-10 19:27:28 -0400131 : QThread(parent),
Zack Rusin404a1ef2011-04-19 23:49:56 -0400132 m_benchmarking(false),
Zack Rusin3acde362011-04-06 01:11:55 -0400133 m_doubleBuffered(true),
Peter Lohrmannb34c6752013-07-10 11:08:14 -0400134 m_singlethread(false),
José Fonsecac03e4b02014-05-30 17:24:42 +0100135 m_useCoreProfile(false),
Zack Rusin3acde362011-04-06 01:11:55 -0400136 m_captureState(false),
José Fonsecac03e4b02014-05-30 17:24:42 +0100137 m_captureThumbnails(false),
James Bentonfc4f55a2012-08-08 17:09:07 +0100138 m_captureCall(0),
139 m_profileGpu(false),
140 m_profileCpu(false),
BogDan Vatraa9f9e642015-02-10 14:31:17 +0200141 m_profilePixels(false),
142 m_profileMemory(false)
Zack Rusin3acde362011-04-06 01:11:55 -0400143{
José Fonseca5bba4772012-03-25 12:46:04 +0100144 qRegisterMetaType<QList<ApiTraceError> >();
Zack Rusin3acde362011-04-06 01:11:55 -0400145}
146
147QString Retracer::fileName() const
148{
149 return m_fileName;
150}
151
152void Retracer::setFileName(const QString &name)
153{
154 m_fileName = name;
155}
156
Carl Worth7257dfc2012-08-09 08:21:42 -0700157QString Retracer::remoteTarget() const
158{
159 return m_remoteTarget;
160}
161
162void Retracer::setRemoteTarget(const QString &host)
163{
164 m_remoteTarget = host;
165}
166
José Fonseca62997b42011-11-27 15:16:34 +0000167void Retracer::setAPI(trace::API api)
168{
169 m_api = api;
170}
171
Zack Rusin3acde362011-04-06 01:11:55 -0400172bool Retracer::isBenchmarking() const
173{
174 return m_benchmarking;
175}
176
177void Retracer::setBenchmarking(bool bench)
178{
179 m_benchmarking = bench;
180}
181
182bool Retracer::isDoubleBuffered() const
183{
184 return m_doubleBuffered;
185}
186
187void Retracer::setDoubleBuffered(bool db)
188{
189 m_doubleBuffered = db;
190}
191
Peter Lohrmannb34c6752013-07-10 11:08:14 -0400192bool Retracer::isSinglethread() const
193{
194 return m_singlethread;
195}
196
197void Retracer::setSinglethread(bool singlethread)
198{
199 m_singlethread = singlethread;
200}
201
Corey Richardsonf3006462014-01-26 17:15:42 -0500202bool Retracer::isCoreProfile() const
203{
204 return m_useCoreProfile;
205}
206
207void Retracer::setCoreProfile(bool coreprofile)
208{
209 m_useCoreProfile = coreprofile;
210}
211
James Bentonfc4f55a2012-08-08 17:09:07 +0100212bool Retracer::isProfilingGpu() const
213{
214 return m_profileGpu;
215}
216
217bool Retracer::isProfilingCpu() const
218{
219 return m_profileCpu;
220}
221
222bool Retracer::isProfilingPixels() const
223{
224 return m_profilePixels;
225}
226
BogDan Vatraa9f9e642015-02-10 14:31:17 +0200227bool Retracer::isProfilingMemory() const
James Bentonfc4f55a2012-08-08 17:09:07 +0100228{
BogDan Vatraa9f9e642015-02-10 14:31:17 +0200229 return m_profileMemory;
James Bentonfc4f55a2012-08-08 17:09:07 +0100230}
231
BogDan Vatraa9f9e642015-02-10 14:31:17 +0200232bool Retracer::isProfiling() const
233{
234 return m_profileGpu || m_profileCpu || m_profilePixels | m_profileMemory;
235}
236
237void Retracer::setProfiling(bool gpu, bool cpu, bool pixels, bool memory)
James Bentonfc4f55a2012-08-08 17:09:07 +0100238{
239 m_profileGpu = gpu;
240 m_profileCpu = cpu;
241 m_profilePixels = pixels;
BogDan Vatraa9f9e642015-02-10 14:31:17 +0200242 m_profileMemory = memory;
James Bentonfc4f55a2012-08-08 17:09:07 +0100243}
244
Zack Rusin3acde362011-04-06 01:11:55 -0400245void Retracer::setCaptureAtCallNumber(qlonglong num)
246{
247 m_captureCall = num;
248}
249
250qlonglong Retracer::captureAtCallNumber() const
251{
252 return m_captureCall;
253}
254
255bool Retracer::captureState() const
256{
257 return m_captureState;
258}
259
260void Retracer::setCaptureState(bool enable)
261{
262 m_captureState = enable;
263}
264
Dan McCabe66dfdda2012-03-05 17:20:39 -0800265bool Retracer::captureThumbnails() const
266{
267 return m_captureThumbnails;
268}
269
270void Retracer::setCaptureThumbnails(bool enable)
271{
272 m_captureThumbnails = enable;
273}
274
Dan McCabe88938852012-06-01 13:40:04 -0700275void Retracer::addThumbnailToCapture(qlonglong num)
276{
277 if (!m_thumbnailsToCapture.contains(num)) {
278 m_thumbnailsToCapture.append(num);
279 }
280}
281
282void Retracer::resetThumbnailsToCapture()
283{
284 m_thumbnailsToCapture.clear();
285}
286
BogDan Vatraa9f9e642015-02-10 14:31:17 +0200287QString Retracer::thumbnailCallSet() const
Dan McCabeb14bda22012-06-01 13:40:06 -0700288{
289 QString callSet;
290
291 bool isFirst = true;
292
293 foreach (qlonglong callIndex, m_thumbnailsToCapture) {
294 // TODO: detect contiguous ranges
295 if (!isFirst) {
296 callSet.append(QLatin1String(","));
297 } else {
298 isFirst = false;
299 }
300
301 //emit "callIndex"
302 callSet.append(QString::number(callIndex));
303 }
304
305 //qDebug() << QLatin1String("debug: call set to capture: ") << callSet;
306 return callSet;
307}
Dan McCabe88938852012-06-01 13:40:04 -0700308
BogDan Vatraa9f9e642015-02-10 14:31:17 +0200309QStringList Retracer::retraceArguments() const
310{
311 QStringList arguments;
312
313 if (m_singlethread) {
314 arguments << QLatin1String("--singlethread");
315 }
316
317 if (m_useCoreProfile) {
318 arguments << QLatin1String("--core");
319 }
320
321 if (m_captureState) {
322 arguments << QLatin1String("-D");
323 arguments << QString::number(m_captureCall);
Jose Fonsecac296a3e2015-05-01 17:43:54 +0100324 arguments << QLatin1String("--dump-format");
325 arguments << QLatin1String("ubjson");
BogDan Vatraa9f9e642015-02-10 14:31:17 +0200326 } else if (m_captureThumbnails) {
327 if (!m_thumbnailsToCapture.isEmpty()) {
328 arguments << QLatin1String("-S");
329 arguments << thumbnailCallSet();
330 }
331 arguments << QLatin1String("-s"); // emit snapshots
332 arguments << QLatin1String("-"); // emit to stdout
333 } else if (isProfiling()) {
334 if (m_profileGpu) {
335 arguments << QLatin1String("--pgpu");
336 }
337
338 if (m_profileCpu) {
339 arguments << QLatin1String("--pcpu");
340 }
341
342 if (m_profilePixels) {
343 arguments << QLatin1String("--ppd");
344 }
345
346 if (m_profileMemory) {
347 arguments << QLatin1String("--pmem");
348 }
349 } else {
350 if (!m_doubleBuffered) {
351 arguments << QLatin1String("--sb");
352 }
353
354 if (m_benchmarking) {
355 arguments << QLatin1String("-b");
356 } else {
357 arguments << QLatin1String("-d");
358 }
359 }
360 return arguments;
361}
362
José Fonseca5bba4772012-03-25 12:46:04 +0100363/**
364 * Starting point for the retracing thread.
365 *
366 * Overrides QThread::run().
367 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400368void Retracer::run()
369{
José Fonseca126f64b2012-03-28 00:13:55 +0100370 QString msg = QLatin1String("Replay finished!");
Zack Rusinf389ae82011-04-10 19:27:28 -0400371
José Fonseca5bba4772012-03-25 12:46:04 +0100372 /*
373 * Construct command line
374 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400375
José Fonseca62997b42011-11-27 15:16:34 +0000376 QString prog;
Zack Rusinf389ae82011-04-10 19:27:28 -0400377 QStringList arguments;
Zack Rusin16ae0362011-04-11 21:30:04 -0400378
José Fonseca889d32c2012-04-23 10:18:28 +0100379 switch (m_api) {
380 case trace::API_GL:
José Fonseca62997b42011-11-27 15:16:34 +0000381 prog = QLatin1String("glretrace");
José Fonseca889d32c2012-04-23 10:18:28 +0100382 break;
383 case trace::API_EGL:
José Fonseca62997b42011-11-27 15:16:34 +0000384 prog = QLatin1String("eglretrace");
José Fonseca889d32c2012-04-23 10:18:28 +0100385 break;
386 case trace::API_DX:
387 case trace::API_D3D7:
388 case trace::API_D3D8:
389 case trace::API_D3D9:
José Fonsecae51e22f2012-12-07 07:48:10 +0000390 case trace::API_DXGI:
José Fonseca889d32c2012-04-23 10:18:28 +0100391#ifdef Q_OS_WIN
392 prog = QLatin1String("d3dretrace");
393#else
394 prog = QLatin1String("wine");
395 arguments << QLatin1String("d3dretrace.exe");
396#endif
397 break;
398 default:
José Fonseca67964382012-03-27 23:54:30 +0100399 emit finished(QLatin1String("Unsupported API"));
José Fonseca62997b42011-11-27 15:16:34 +0000400 return;
401 }
402
BogDan Vatraa9f9e642015-02-10 14:31:17 +0200403 arguments << retraceArguments() << m_fileName;
Zack Rusinf389ae82011-04-10 19:27:28 -0400404
José Fonseca5bba4772012-03-25 12:46:04 +0100405 /*
Carl Worth7257dfc2012-08-09 08:21:42 -0700406 * Support remote execution on a separate target.
407 */
408
409 if (m_remoteTarget.length() != 0) {
410 arguments.prepend(prog);
411 arguments.prepend(m_remoteTarget);
412 prog = QLatin1String("ssh");
413 }
414
415 /*
José Fonseca5bba4772012-03-25 12:46:04 +0100416 * Start the process.
417 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400418
José Fonseca11ad0ea2014-11-15 09:58:44 +0000419 {
420 QDebug debug(QtDebugMsg);
421 debug << "Running:";
422 debug << prog;
423 foreach (const QString &argument, arguments) {
424 debug << argument;
425 }
426 }
427
José Fonseca5bba4772012-03-25 12:46:04 +0100428 QProcess process;
Zack Rusinf389ae82011-04-10 19:27:28 -0400429
José Fonseca6ea8dee2012-03-25 17:25:24 +0100430 process.start(prog, arguments, QIODevice::ReadOnly);
José Fonseca5bba4772012-03-25 12:46:04 +0100431 if (!process.waitForStarted(-1)) {
432 emit finished(QLatin1String("Could not start process"));
433 return;
434 }
José Fonseca56cd8ac2011-11-24 16:30:49 +0000435
José Fonseca5bba4772012-03-25 12:46:04 +0100436 /*
437 * Process standard output
438 */
439
Dan McCabec6f924e2012-06-01 13:40:05 -0700440 ImageHash thumbnails;
José Fonseca5bba4772012-03-25 12:46:04 +0100441 QVariantMap parsedJson;
James Bentonfc4f55a2012-08-08 17:09:07 +0100442 trace::Profile* profile = NULL;
José Fonseca5bba4772012-03-25 12:46:04 +0100443
444 process.setReadChannel(QProcess::StandardOutput);
445 if (process.waitForReadyRead(-1)) {
José Fonseca6ea8dee2012-03-25 17:25:24 +0100446 BlockingIODevice io(&process);
447
José Fonseca5bba4772012-03-25 12:46:04 +0100448 if (m_captureState) {
Jose Fonseca08fdb7b2015-05-21 21:41:50 +0100449 parsedJson = decodeUBJSONObject(&io).toMap();
José Fonseca95b40562012-04-05 20:06:42 +0100450 process.waitForFinished(-1);
José Fonseca5bba4772012-03-25 12:46:04 +0100451 } else if (m_captureThumbnails) {
452 /*
453 * Parse concatenated PNM images from output.
454 */
Dan McCabe66dfdda2012-03-05 17:20:39 -0800455
José Fonseca6ea8dee2012-03-25 17:25:24 +0100456 while (!io.atEnd()) {
José Fonsecabeda4442013-09-12 17:25:04 +0100457 image::PNMInfo info;
José Fonseca5bba4772012-03-25 12:46:04 +0100458
459 char header[512];
460 qint64 headerSize = 0;
461 int headerLines = 3; // assume no optional comment line
462
463 for (int headerLine = 0; headerLine < headerLines; ++headerLine) {
José Fonseca6ea8dee2012-03-25 17:25:24 +0100464 qint64 headerRead = io.readLine(&header[headerSize], sizeof(header) - headerSize);
José Fonseca5bba4772012-03-25 12:46:04 +0100465
466 // if header actually contains optional comment line, ...
467 if (headerLine == 1 && header[headerSize] == '#') {
468 ++headerLines;
469 }
470
471 headerSize += headerRead;
472 }
473
José Fonsecabeda4442013-09-12 17:25:04 +0100474 const char *headerEnd = image::readPNMHeader(header, headerSize, info);
José Fonseca5bba4772012-03-25 12:46:04 +0100475
476 // if invalid PNM header was encountered, ...
José Fonsecabeda4442013-09-12 17:25:04 +0100477 if (headerEnd == NULL ||
478 info.channelType != image::TYPE_UNORM8) {
José Fonseca5bba4772012-03-25 12:46:04 +0100479 qDebug() << "error: invalid snapshot stream encountered";
480 break;
481 }
482
José Fonsecabeda4442013-09-12 17:25:04 +0100483 unsigned channels = info.channels;
484 unsigned width = info.width;
485 unsigned height = info.height;
486
José Fonseca5bba4772012-03-25 12:46:04 +0100487 // qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height";
488
489 QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888);
490
491 int rowBytes = channels * width;
492 for (int y = 0; y < height; ++y) {
493 unsigned char *scanLine = snapshot.scanLine(y);
José Fonseca6ea8dee2012-03-25 17:25:24 +0100494 qint64 readBytes = io.read((char *) scanLine, rowBytes);
495 Q_ASSERT(readBytes == rowBytes);
José Fonsecaa2bf2872012-11-15 13:35:22 +0000496 (void)readBytes;
José Fonseca5bba4772012-03-25 12:46:04 +0100497 }
498
José Fonsecadc9e9c62012-03-26 10:29:32 +0100499 QImage thumb = thumbnail(snapshot);
Dan McCabec6f924e2012-06-01 13:40:05 -0700500 thumbnails.insert(info.commentNumber, thumb);
Dan McCabe66dfdda2012-03-05 17:20:39 -0800501 }
José Fonseca5bba4772012-03-25 12:46:04 +0100502
503 Q_ASSERT(process.state() != QProcess::Running);
James Bentonfc4f55a2012-08-08 17:09:07 +0100504 } else if (isProfiling()) {
505 profile = new trace::Profile();
José Fonseca5bba4772012-03-25 12:46:04 +0100506
James Bentonfc4f55a2012-08-08 17:09:07 +0100507 while (!io.atEnd()) {
508 char line[256];
509 qint64 lineLength;
510
511 lineLength = io.readLine(line, 256);
512
513 if (lineLength == -1)
514 break;
515
516 trace::Profiler::parseLine(line, profile);
517 }
José Fonseca56cd8ac2011-11-24 16:30:49 +0000518 } else {
José Fonseca676cd172012-03-24 09:46:24 +0000519 QByteArray output;
José Fonseca5bba4772012-03-25 12:46:04 +0100520 output = process.readAllStandardOutput();
José Fonseca126f64b2012-03-28 00:13:55 +0100521 if (output.length() < 80) {
522 msg = QString::fromUtf8(output);
523 }
José Fonseca56cd8ac2011-11-24 16:30:49 +0000524 }
Zack Rusinf389ae82011-04-10 19:27:28 -0400525 }
526
José Fonseca5bba4772012-03-25 12:46:04 +0100527 /*
528 * Wait for process termination
529 */
530
531 process.waitForFinished(-1);
532
533 if (process.exitStatus() != QProcess::NormalExit) {
534 msg = QLatin1String("Process crashed");
535 } else if (process.exitCode() != 0) {
536 msg = QLatin1String("Process exited with non zero exit code");
537 }
538
539 /*
540 * Parse errors.
541 */
542
Zack Rusin10fd4772011-09-14 01:45:12 -0400543 QList<ApiTraceError> errors;
José Fonseca5bba4772012-03-25 12:46:04 +0100544 process.setReadChannel(QProcess::StandardError);
José Fonseca8fdf56c2012-03-24 10:06:56 +0000545 QRegExp regexp("(^\\d+): +(\\b\\w+\\b): ([^\\r\\n]+)[\\r\\n]*$");
José Fonseca5bba4772012-03-25 12:46:04 +0100546 while (!process.atEnd()) {
547 QString line = process.readLine();
Zack Rusinb39e1c62011-04-19 23:09:26 -0400548 if (regexp.indexIn(line) != -1) {
Zack Rusin10fd4772011-09-14 01:45:12 -0400549 ApiTraceError error;
Zack Rusinb39e1c62011-04-19 23:09:26 -0400550 error.callIndex = regexp.cap(1).toInt();
551 error.type = regexp.cap(2);
552 error.message = regexp.cap(3);
553 errors.append(error);
gregoryf2329b62012-07-06 21:48:59 +0200554 } else if (!errors.isEmpty()) {
555 // Probably a multiligne message
556 ApiTraceError &previous = errors.last();
557 if (line.endsWith("\n")) {
558 line.chop(1);
559 }
560 previous.message.append('\n');
561 previous.message.append(line);
Zack Rusinb39e1c62011-04-19 23:09:26 -0400562 }
563 }
José Fonseca5bba4772012-03-25 12:46:04 +0100564
565 /*
566 * Emit signals
567 */
568
569 if (m_captureState) {
570 ApiTraceState *state = new ApiTraceState(parsedJson);
571 emit foundState(state);
José Fonseca5bba4772012-03-25 12:46:04 +0100572 }
573
574 if (m_captureThumbnails && !thumbnails.isEmpty()) {
575 emit foundThumbnails(thumbnails);
576 }
577
James Bentonfc4f55a2012-08-08 17:09:07 +0100578 if (isProfiling() && profile) {
579 emit foundProfile(profile);
580 }
581
Zack Rusinb39e1c62011-04-19 23:09:26 -0400582 if (!errors.isEmpty()) {
583 emit retraceErrors(errors);
584 }
José Fonseca5bba4772012-03-25 12:46:04 +0100585
Zack Rusinf389ae82011-04-10 19:27:28 -0400586 emit finished(msg);
Zack Rusin3acde362011-04-06 01:11:55 -0400587}
588
Zack Rusin3acde362011-04-06 01:11:55 -0400589#include "retracer.moc"