blob: 8af7cd8a9a882c9237ad0703121cb97323cdf342 [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>
José Fonseca3f4cd302015-01-14 12:02:06 +000014#include <QJsonDocument>
Zack Rusin3acde362011-04-06 01:11:55 -040015
José Fonseca6ea8dee2012-03-25 17:25:24 +010016/**
17 * Wrapper around a QProcess which enforces IO to block .
18 *
19 * Several QIODevice users (notably QJSON) expect blocking semantics, e.g.,
20 * they expect that QIODevice::read() will blocked until the requested ammount
21 * of bytes is read or end of file is reached. But by default QProcess, does
22 * not block. And passing QIODevice::Unbuffered mitigates but does not fully
23 * address the problem either.
24 *
25 * This class wraps around QProcess, providing QIODevice interface, while
26 * ensuring that all reads block.
27 *
28 * This class also works around a bug in QProcess::atEnd() implementation.
29 *
30 * See also:
31 * - http://qt-project.org/wiki/Simple_Crypt_IO_Device
32 * - http://qt-project.org/wiki/Custom_IO_Device
33 */
34class BlockingIODevice : public QIODevice
35{
36 /* We don't use the Q_OBJECT in this class given we don't declare any
37 * signals and slots or use any other services provided by Qt's meta-object
38 * system. */
39public:
40 BlockingIODevice(QProcess * io);
41 bool isSequential() const;
42 bool atEnd() const;
43 bool waitForReadyRead(int msecs = -1);
44
45protected:
46 qint64 readData(char * data, qint64 maxSize);
47 qint64 writeData(const char * data, qint64 maxSize);
48
49private:
50 QProcess *m_device;
51};
52
53BlockingIODevice::BlockingIODevice(QProcess * io) :
54 m_device(io)
55{
56 /*
57 * We pass QIODevice::Unbuffered to prevent the base QIODevice class to do
58 * its own buffering on top of the overridden readData() method.
59 *
60 * The only buffering used will be to satisfy QIODevice::peek() and
61 * QIODevice::ungetChar().
62 */
63 setOpenMode(ReadOnly | Unbuffered);
64}
65
66bool BlockingIODevice::isSequential() const
67{
68 return true;
69}
70
71bool BlockingIODevice::atEnd() const
72{
73 /*
74 * XXX: QProcess::atEnd() documentation is wrong -- it will return true
75 * even when the process is running --, so we try to workaround that here.
76 */
77 if (m_device->atEnd()) {
78 if (m_device->state() == QProcess::Running) {
79 if (!m_device->waitForReadyRead(-1)) {
80 return true;
81 }
82 }
83 }
84 return false;
85}
86
87bool BlockingIODevice::waitForReadyRead(int msecs)
88{
89 Q_UNUSED(msecs);
90 return true;
91}
92
93qint64 BlockingIODevice::readData(char * data, qint64 maxSize)
94{
95 qint64 bytesToRead = maxSize;
96 qint64 readSoFar = 0;
97 do {
98 qint64 chunkSize = m_device->read(data + readSoFar, bytesToRead);
99 if (chunkSize < 0) {
100 if (readSoFar) {
101 return readSoFar;
102 } else {
103 return chunkSize;
104 }
105 }
106 Q_ASSERT(chunkSize <= bytesToRead);
107 bytesToRead -= chunkSize;
108 readSoFar += chunkSize;
109 if (bytesToRead) {
110 if (!m_device->waitForReadyRead(-1)) {
111 qDebug() << "waitForReadyRead failed\n";
112 break;
113 }
114 }
115 } while(bytesToRead);
116
117 return readSoFar;
118}
119
120qint64 BlockingIODevice::writeData(const char * data, qint64 maxSize)
121{
122 Q_ASSERT(false);
123 return -1;
124}
125
José Fonseca5bba4772012-03-25 12:46:04 +0100126Q_DECLARE_METATYPE(QList<ApiTraceError>);
127
Zack Rusin3acde362011-04-06 01:11:55 -0400128Retracer::Retracer(QObject *parent)
Zack Rusinf389ae82011-04-10 19:27:28 -0400129 : QThread(parent),
Zack Rusin404a1ef2011-04-19 23:49:56 -0400130 m_benchmarking(false),
Zack Rusin3acde362011-04-06 01:11:55 -0400131 m_doubleBuffered(true),
Peter Lohrmannb34c6752013-07-10 11:08:14 -0400132 m_singlethread(false),
José Fonsecac03e4b02014-05-30 17:24:42 +0100133 m_useCoreProfile(false),
Zack Rusin3acde362011-04-06 01:11:55 -0400134 m_captureState(false),
José Fonsecac03e4b02014-05-30 17:24:42 +0100135 m_captureThumbnails(false),
James Bentonfc4f55a2012-08-08 17:09:07 +0100136 m_captureCall(0),
137 m_profileGpu(false),
138 m_profileCpu(false),
BogDan Vatraa9f9e642015-02-10 14:31:17 +0200139 m_profilePixels(false),
140 m_profileMemory(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
BogDan Vatraa9f9e642015-02-10 14:31:17 +0200225bool Retracer::isProfilingMemory() const
James Bentonfc4f55a2012-08-08 17:09:07 +0100226{
BogDan Vatraa9f9e642015-02-10 14:31:17 +0200227 return m_profileMemory;
James Bentonfc4f55a2012-08-08 17:09:07 +0100228}
229
BogDan Vatraa9f9e642015-02-10 14:31:17 +0200230bool Retracer::isProfiling() const
231{
232 return m_profileGpu || m_profileCpu || m_profilePixels | m_profileMemory;
233}
234
235void Retracer::setProfiling(bool gpu, bool cpu, bool pixels, bool memory)
James Bentonfc4f55a2012-08-08 17:09:07 +0100236{
237 m_profileGpu = gpu;
238 m_profileCpu = cpu;
239 m_profilePixels = pixels;
BogDan Vatraa9f9e642015-02-10 14:31:17 +0200240 m_profileMemory = memory;
James Bentonfc4f55a2012-08-08 17:09:07 +0100241}
242
Zack Rusin3acde362011-04-06 01:11:55 -0400243void Retracer::setCaptureAtCallNumber(qlonglong num)
244{
245 m_captureCall = num;
246}
247
248qlonglong Retracer::captureAtCallNumber() const
249{
250 return m_captureCall;
251}
252
253bool Retracer::captureState() const
254{
255 return m_captureState;
256}
257
258void Retracer::setCaptureState(bool enable)
259{
260 m_captureState = enable;
261}
262
Dan McCabe66dfdda2012-03-05 17:20:39 -0800263bool Retracer::captureThumbnails() const
264{
265 return m_captureThumbnails;
266}
267
268void Retracer::setCaptureThumbnails(bool enable)
269{
270 m_captureThumbnails = enable;
271}
272
Dan McCabe88938852012-06-01 13:40:04 -0700273void Retracer::addThumbnailToCapture(qlonglong num)
274{
275 if (!m_thumbnailsToCapture.contains(num)) {
276 m_thumbnailsToCapture.append(num);
277 }
278}
279
280void Retracer::resetThumbnailsToCapture()
281{
282 m_thumbnailsToCapture.clear();
283}
284
BogDan Vatraa9f9e642015-02-10 14:31:17 +0200285QString Retracer::thumbnailCallSet() const
Dan McCabeb14bda22012-06-01 13:40:06 -0700286{
287 QString callSet;
288
289 bool isFirst = true;
290
291 foreach (qlonglong callIndex, m_thumbnailsToCapture) {
292 // TODO: detect contiguous ranges
293 if (!isFirst) {
294 callSet.append(QLatin1String(","));
295 } else {
296 isFirst = false;
297 }
298
299 //emit "callIndex"
300 callSet.append(QString::number(callIndex));
301 }
302
303 //qDebug() << QLatin1String("debug: call set to capture: ") << callSet;
304 return callSet;
305}
Dan McCabe88938852012-06-01 13:40:04 -0700306
BogDan Vatraa9f9e642015-02-10 14:31:17 +0200307QStringList Retracer::retraceArguments() const
308{
309 QStringList arguments;
310
311 if (m_singlethread) {
312 arguments << QLatin1String("--singlethread");
313 }
314
315 if (m_useCoreProfile) {
316 arguments << QLatin1String("--core");
317 }
318
319 if (m_captureState) {
320 arguments << QLatin1String("-D");
321 arguments << QString::number(m_captureCall);
322 } else if (m_captureThumbnails) {
323 if (!m_thumbnailsToCapture.isEmpty()) {
324 arguments << QLatin1String("-S");
325 arguments << thumbnailCallSet();
326 }
327 arguments << QLatin1String("-s"); // emit snapshots
328 arguments << QLatin1String("-"); // emit to stdout
329 } else if (isProfiling()) {
330 if (m_profileGpu) {
331 arguments << QLatin1String("--pgpu");
332 }
333
334 if (m_profileCpu) {
335 arguments << QLatin1String("--pcpu");
336 }
337
338 if (m_profilePixels) {
339 arguments << QLatin1String("--ppd");
340 }
341
342 if (m_profileMemory) {
343 arguments << QLatin1String("--pmem");
344 }
345 } else {
346 if (!m_doubleBuffered) {
347 arguments << QLatin1String("--sb");
348 }
349
350 if (m_benchmarking) {
351 arguments << QLatin1String("-b");
352 } else {
353 arguments << QLatin1String("-d");
354 }
355 }
356 return arguments;
357}
358
José Fonseca5bba4772012-03-25 12:46:04 +0100359/**
360 * Starting point for the retracing thread.
361 *
362 * Overrides QThread::run().
363 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400364void Retracer::run()
365{
José Fonseca126f64b2012-03-28 00:13:55 +0100366 QString msg = QLatin1String("Replay finished!");
Zack Rusinf389ae82011-04-10 19:27:28 -0400367
José Fonseca5bba4772012-03-25 12:46:04 +0100368 /*
369 * Construct command line
370 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400371
José Fonseca62997b42011-11-27 15:16:34 +0000372 QString prog;
Zack Rusinf389ae82011-04-10 19:27:28 -0400373 QStringList arguments;
Zack Rusin16ae0362011-04-11 21:30:04 -0400374
José Fonseca889d32c2012-04-23 10:18:28 +0100375 switch (m_api) {
376 case trace::API_GL:
José Fonseca62997b42011-11-27 15:16:34 +0000377 prog = QLatin1String("glretrace");
José Fonseca889d32c2012-04-23 10:18:28 +0100378 break;
379 case trace::API_EGL:
José Fonseca62997b42011-11-27 15:16:34 +0000380 prog = QLatin1String("eglretrace");
José Fonseca889d32c2012-04-23 10:18:28 +0100381 break;
382 case trace::API_DX:
383 case trace::API_D3D7:
384 case trace::API_D3D8:
385 case trace::API_D3D9:
José Fonsecae51e22f2012-12-07 07:48:10 +0000386 case trace::API_DXGI:
José Fonseca889d32c2012-04-23 10:18:28 +0100387#ifdef Q_OS_WIN
388 prog = QLatin1String("d3dretrace");
389#else
390 prog = QLatin1String("wine");
391 arguments << QLatin1String("d3dretrace.exe");
392#endif
393 break;
394 default:
José Fonseca67964382012-03-27 23:54:30 +0100395 emit finished(QLatin1String("Unsupported API"));
José Fonseca62997b42011-11-27 15:16:34 +0000396 return;
397 }
398
BogDan Vatraa9f9e642015-02-10 14:31:17 +0200399 arguments << retraceArguments() << m_fileName;
Zack Rusinf389ae82011-04-10 19:27:28 -0400400
José Fonseca5bba4772012-03-25 12:46:04 +0100401 /*
Carl Worth7257dfc2012-08-09 08:21:42 -0700402 * Support remote execution on a separate target.
403 */
404
405 if (m_remoteTarget.length() != 0) {
406 arguments.prepend(prog);
407 arguments.prepend(m_remoteTarget);
408 prog = QLatin1String("ssh");
409 }
410
411 /*
José Fonseca5bba4772012-03-25 12:46:04 +0100412 * Start the process.
413 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400414
José Fonseca11ad0ea2014-11-15 09:58:44 +0000415 {
416 QDebug debug(QtDebugMsg);
417 debug << "Running:";
418 debug << prog;
419 foreach (const QString &argument, arguments) {
420 debug << argument;
421 }
422 }
423
José Fonseca5bba4772012-03-25 12:46:04 +0100424 QProcess process;
Zack Rusinf389ae82011-04-10 19:27:28 -0400425
José Fonseca6ea8dee2012-03-25 17:25:24 +0100426 process.start(prog, arguments, QIODevice::ReadOnly);
José Fonseca5bba4772012-03-25 12:46:04 +0100427 if (!process.waitForStarted(-1)) {
428 emit finished(QLatin1String("Could not start process"));
429 return;
430 }
José Fonseca56cd8ac2011-11-24 16:30:49 +0000431
José Fonseca5bba4772012-03-25 12:46:04 +0100432 /*
433 * Process standard output
434 */
435
Dan McCabec6f924e2012-06-01 13:40:05 -0700436 ImageHash thumbnails;
José Fonseca5bba4772012-03-25 12:46:04 +0100437 QVariantMap parsedJson;
James Bentonfc4f55a2012-08-08 17:09:07 +0100438 trace::Profile* profile = NULL;
José Fonseca5bba4772012-03-25 12:46:04 +0100439
440 process.setReadChannel(QProcess::StandardOutput);
441 if (process.waitForReadyRead(-1)) {
José Fonseca6ea8dee2012-03-25 17:25:24 +0100442 BlockingIODevice io(&process);
443
José Fonseca5bba4772012-03-25 12:46:04 +0100444 if (m_captureState) {
José Fonseca95b40562012-04-05 20:06:42 +0100445 process.waitForFinished(-1);
José Fonseca3f4cd302015-01-14 12:02:06 +0000446 QByteArray data = process.readAll();
447 QJsonParseError error;
448 QJsonDocument jsonDoc =
449 QJsonDocument::fromJson(data, &error);
450
451 if (error.error != QJsonParseError::NoError) {
452 //qDebug()<<"Error is "<<error.errorString();
453 msg = error.errorString();
José Fonseca5bba4772012-03-25 12:46:04 +0100454 }
José Fonseca3f4cd302015-01-14 12:02:06 +0000455 parsedJson = jsonDoc.toVariant().toMap();
José Fonseca5bba4772012-03-25 12:46:04 +0100456 } else if (m_captureThumbnails) {
457 /*
458 * Parse concatenated PNM images from output.
459 */
Dan McCabe66dfdda2012-03-05 17:20:39 -0800460
José Fonseca6ea8dee2012-03-25 17:25:24 +0100461 while (!io.atEnd()) {
José Fonsecabeda4442013-09-12 17:25:04 +0100462 image::PNMInfo info;
José Fonseca5bba4772012-03-25 12:46:04 +0100463
464 char header[512];
465 qint64 headerSize = 0;
466 int headerLines = 3; // assume no optional comment line
467
468 for (int headerLine = 0; headerLine < headerLines; ++headerLine) {
José Fonseca6ea8dee2012-03-25 17:25:24 +0100469 qint64 headerRead = io.readLine(&header[headerSize], sizeof(header) - headerSize);
José Fonseca5bba4772012-03-25 12:46:04 +0100470
471 // if header actually contains optional comment line, ...
472 if (headerLine == 1 && header[headerSize] == '#') {
473 ++headerLines;
474 }
475
476 headerSize += headerRead;
477 }
478
José Fonsecabeda4442013-09-12 17:25:04 +0100479 const char *headerEnd = image::readPNMHeader(header, headerSize, info);
José Fonseca5bba4772012-03-25 12:46:04 +0100480
481 // if invalid PNM header was encountered, ...
José Fonsecabeda4442013-09-12 17:25:04 +0100482 if (headerEnd == NULL ||
483 info.channelType != image::TYPE_UNORM8) {
José Fonseca5bba4772012-03-25 12:46:04 +0100484 qDebug() << "error: invalid snapshot stream encountered";
485 break;
486 }
487
José Fonsecabeda4442013-09-12 17:25:04 +0100488 unsigned channels = info.channels;
489 unsigned width = info.width;
490 unsigned height = info.height;
491
José Fonseca5bba4772012-03-25 12:46:04 +0100492 // qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height";
493
494 QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888);
495
496 int rowBytes = channels * width;
497 for (int y = 0; y < height; ++y) {
498 unsigned char *scanLine = snapshot.scanLine(y);
José Fonseca6ea8dee2012-03-25 17:25:24 +0100499 qint64 readBytes = io.read((char *) scanLine, rowBytes);
500 Q_ASSERT(readBytes == rowBytes);
José Fonsecaa2bf2872012-11-15 13:35:22 +0000501 (void)readBytes;
José Fonseca5bba4772012-03-25 12:46:04 +0100502 }
503
José Fonsecadc9e9c62012-03-26 10:29:32 +0100504 QImage thumb = thumbnail(snapshot);
Dan McCabec6f924e2012-06-01 13:40:05 -0700505 thumbnails.insert(info.commentNumber, thumb);
Dan McCabe66dfdda2012-03-05 17:20:39 -0800506 }
José Fonseca5bba4772012-03-25 12:46:04 +0100507
508 Q_ASSERT(process.state() != QProcess::Running);
James Bentonfc4f55a2012-08-08 17:09:07 +0100509 } else if (isProfiling()) {
510 profile = new trace::Profile();
José Fonseca5bba4772012-03-25 12:46:04 +0100511
James Bentonfc4f55a2012-08-08 17:09:07 +0100512 while (!io.atEnd()) {
513 char line[256];
514 qint64 lineLength;
515
516 lineLength = io.readLine(line, 256);
517
518 if (lineLength == -1)
519 break;
520
521 trace::Profiler::parseLine(line, profile);
522 }
José Fonseca56cd8ac2011-11-24 16:30:49 +0000523 } else {
José Fonseca676cd172012-03-24 09:46:24 +0000524 QByteArray output;
José Fonseca5bba4772012-03-25 12:46:04 +0100525 output = process.readAllStandardOutput();
José Fonseca126f64b2012-03-28 00:13:55 +0100526 if (output.length() < 80) {
527 msg = QString::fromUtf8(output);
528 }
José Fonseca56cd8ac2011-11-24 16:30:49 +0000529 }
Zack Rusinf389ae82011-04-10 19:27:28 -0400530 }
531
José Fonseca5bba4772012-03-25 12:46:04 +0100532 /*
533 * Wait for process termination
534 */
535
536 process.waitForFinished(-1);
537
538 if (process.exitStatus() != QProcess::NormalExit) {
539 msg = QLatin1String("Process crashed");
540 } else if (process.exitCode() != 0) {
541 msg = QLatin1String("Process exited with non zero exit code");
542 }
543
544 /*
545 * Parse errors.
546 */
547
Zack Rusin10fd4772011-09-14 01:45:12 -0400548 QList<ApiTraceError> errors;
José Fonseca5bba4772012-03-25 12:46:04 +0100549 process.setReadChannel(QProcess::StandardError);
José Fonseca8fdf56c2012-03-24 10:06:56 +0000550 QRegExp regexp("(^\\d+): +(\\b\\w+\\b): ([^\\r\\n]+)[\\r\\n]*$");
José Fonseca5bba4772012-03-25 12:46:04 +0100551 while (!process.atEnd()) {
552 QString line = process.readLine();
Zack Rusinb39e1c62011-04-19 23:09:26 -0400553 if (regexp.indexIn(line) != -1) {
Zack Rusin10fd4772011-09-14 01:45:12 -0400554 ApiTraceError error;
Zack Rusinb39e1c62011-04-19 23:09:26 -0400555 error.callIndex = regexp.cap(1).toInt();
556 error.type = regexp.cap(2);
557 error.message = regexp.cap(3);
558 errors.append(error);
gregoryf2329b62012-07-06 21:48:59 +0200559 } else if (!errors.isEmpty()) {
560 // Probably a multiligne message
561 ApiTraceError &previous = errors.last();
562 if (line.endsWith("\n")) {
563 line.chop(1);
564 }
565 previous.message.append('\n');
566 previous.message.append(line);
Zack Rusinb39e1c62011-04-19 23:09:26 -0400567 }
568 }
José Fonseca5bba4772012-03-25 12:46:04 +0100569
570 /*
571 * Emit signals
572 */
573
574 if (m_captureState) {
575 ApiTraceState *state = new ApiTraceState(parsedJson);
576 emit foundState(state);
José Fonseca5bba4772012-03-25 12:46:04 +0100577 }
578
579 if (m_captureThumbnails && !thumbnails.isEmpty()) {
580 emit foundThumbnails(thumbnails);
581 }
582
James Bentonfc4f55a2012-08-08 17:09:07 +0100583 if (isProfiling() && profile) {
584 emit foundProfile(profile);
585 }
586
Zack Rusinb39e1c62011-04-19 23:09:26 -0400587 if (!errors.isEmpty()) {
588 emit retraceErrors(errors);
589 }
José Fonseca5bba4772012-03-25 12:46:04 +0100590
Zack Rusinf389ae82011-04-10 19:27:28 -0400591 emit finished(msg);
Zack Rusin3acde362011-04-06 01:11:55 -0400592}
593
Zack Rusin3acde362011-04-06 01:11:55 -0400594#include "retracer.moc"