blob: bbe638ca0fc0635f41e10ff9ffdbcf225f0bd0a5 [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
Dan McCabe66dfdda2012-03-05 17:20:39 -08006#include "image.hpp"
7
Zack Rusin3acde362011-04-06 01:11:55 -04008#include <QDebug>
Zack Rusinf389ae82011-04-10 19:27:28 -04009#include <QVariant>
Dan McCabe66dfdda2012-03-05 17:20:39 -080010#include <QList>
11#include <QImage>
Zack Rusinf389ae82011-04-10 19:27:28 -040012
13#include <qjson/parser.h>
Zack Rusin3acde362011-04-06 01:11:55 -040014
José Fonseca6ea8dee2012-03-25 17:25:24 +010015/**
16 * Wrapper around a QProcess which enforces IO to block .
17 *
18 * Several QIODevice users (notably QJSON) expect blocking semantics, e.g.,
19 * they expect that QIODevice::read() will blocked until the requested ammount
20 * of bytes is read or end of file is reached. But by default QProcess, does
21 * not block. And passing QIODevice::Unbuffered mitigates but does not fully
22 * address the problem either.
23 *
24 * This class wraps around QProcess, providing QIODevice interface, while
25 * ensuring that all reads block.
26 *
27 * This class also works around a bug in QProcess::atEnd() implementation.
28 *
29 * See also:
30 * - http://qt-project.org/wiki/Simple_Crypt_IO_Device
31 * - http://qt-project.org/wiki/Custom_IO_Device
32 */
33class BlockingIODevice : public QIODevice
34{
35 /* We don't use the Q_OBJECT in this class given we don't declare any
36 * signals and slots or use any other services provided by Qt's meta-object
37 * system. */
38public:
39 BlockingIODevice(QProcess * io);
40 bool isSequential() const;
41 bool atEnd() const;
42 bool waitForReadyRead(int msecs = -1);
43
44protected:
45 qint64 readData(char * data, qint64 maxSize);
46 qint64 writeData(const char * data, qint64 maxSize);
47
48private:
49 QProcess *m_device;
50};
51
52BlockingIODevice::BlockingIODevice(QProcess * io) :
53 m_device(io)
54{
55 /*
56 * We pass QIODevice::Unbuffered to prevent the base QIODevice class to do
57 * its own buffering on top of the overridden readData() method.
58 *
59 * The only buffering used will be to satisfy QIODevice::peek() and
60 * QIODevice::ungetChar().
61 */
62 setOpenMode(ReadOnly | Unbuffered);
63}
64
65bool BlockingIODevice::isSequential() const
66{
67 return true;
68}
69
70bool BlockingIODevice::atEnd() const
71{
72 /*
73 * XXX: QProcess::atEnd() documentation is wrong -- it will return true
74 * even when the process is running --, so we try to workaround that here.
75 */
76 if (m_device->atEnd()) {
77 if (m_device->state() == QProcess::Running) {
78 if (!m_device->waitForReadyRead(-1)) {
79 return true;
80 }
81 }
82 }
83 return false;
84}
85
86bool BlockingIODevice::waitForReadyRead(int msecs)
87{
88 Q_UNUSED(msecs);
89 return true;
90}
91
92qint64 BlockingIODevice::readData(char * data, qint64 maxSize)
93{
94 qint64 bytesToRead = maxSize;
95 qint64 readSoFar = 0;
96 do {
97 qint64 chunkSize = m_device->read(data + readSoFar, bytesToRead);
98 if (chunkSize < 0) {
99 if (readSoFar) {
100 return readSoFar;
101 } else {
102 return chunkSize;
103 }
104 }
105 Q_ASSERT(chunkSize <= bytesToRead);
106 bytesToRead -= chunkSize;
107 readSoFar += chunkSize;
108 if (bytesToRead) {
109 if (!m_device->waitForReadyRead(-1)) {
110 qDebug() << "waitForReadyRead failed\n";
111 break;
112 }
113 }
114 } while(bytesToRead);
115
116 return readSoFar;
117}
118
119qint64 BlockingIODevice::writeData(const char * data, qint64 maxSize)
120{
121 Q_ASSERT(false);
122 return -1;
123}
124
José Fonseca5bba4772012-03-25 12:46:04 +0100125Q_DECLARE_METATYPE(QList<ApiTraceError>);
126
Zack Rusin3acde362011-04-06 01:11:55 -0400127Retracer::Retracer(QObject *parent)
Zack Rusinf389ae82011-04-10 19:27:28 -0400128 : QThread(parent),
Zack Rusin404a1ef2011-04-19 23:49:56 -0400129 m_benchmarking(false),
Zack Rusin3acde362011-04-06 01:11:55 -0400130 m_doubleBuffered(true),
131 m_captureState(false),
Zack Rusinf389ae82011-04-10 19:27:28 -0400132 m_captureCall(0)
Zack Rusin3acde362011-04-06 01:11:55 -0400133{
José Fonseca5bba4772012-03-25 12:46:04 +0100134 qRegisterMetaType<QList<ApiTraceError> >();
135
Zack Rusinf389ae82011-04-10 19:27:28 -0400136#ifdef Q_OS_WIN
137 QString format = QLatin1String("%1;");
138#else
139 QString format = QLatin1String("%1:");
140#endif
José Fonseca27440922011-11-01 08:27:12 +0000141 QString buildPath = format.arg(APITRACE_BINARY_DIR);
Zack Rusinf389ae82011-04-10 19:27:28 -0400142 m_processEnvironment = QProcessEnvironment::systemEnvironment();
143 m_processEnvironment.insert("PATH", buildPath +
144 m_processEnvironment.value("PATH"));
145
146 qputenv("PATH",
147 m_processEnvironment.value("PATH").toLatin1());
Zack Rusin3acde362011-04-06 01:11:55 -0400148}
149
150QString Retracer::fileName() const
151{
152 return m_fileName;
153}
154
155void Retracer::setFileName(const QString &name)
156{
157 m_fileName = name;
158}
159
José Fonseca62997b42011-11-27 15:16:34 +0000160void Retracer::setAPI(trace::API api)
161{
162 m_api = api;
163}
164
Zack Rusin3acde362011-04-06 01:11:55 -0400165bool Retracer::isBenchmarking() const
166{
167 return m_benchmarking;
168}
169
170void Retracer::setBenchmarking(bool bench)
171{
172 m_benchmarking = bench;
173}
174
175bool Retracer::isDoubleBuffered() const
176{
177 return m_doubleBuffered;
178}
179
180void Retracer::setDoubleBuffered(bool db)
181{
182 m_doubleBuffered = db;
183}
184
Zack Rusin3acde362011-04-06 01:11:55 -0400185void Retracer::setCaptureAtCallNumber(qlonglong num)
186{
187 m_captureCall = num;
188}
189
190qlonglong Retracer::captureAtCallNumber() const
191{
192 return m_captureCall;
193}
194
195bool Retracer::captureState() const
196{
197 return m_captureState;
198}
199
200void Retracer::setCaptureState(bool enable)
201{
202 m_captureState = enable;
203}
204
Dan McCabe66dfdda2012-03-05 17:20:39 -0800205bool Retracer::captureThumbnails() const
206{
207 return m_captureThumbnails;
208}
209
210void Retracer::setCaptureThumbnails(bool enable)
211{
212 m_captureThumbnails = enable;
213}
214
Zack Rusinf389ae82011-04-10 19:27:28 -0400215
José Fonseca5bba4772012-03-25 12:46:04 +0100216/**
217 * Starting point for the retracing thread.
218 *
219 * Overrides QThread::run().
220 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400221void Retracer::run()
222{
José Fonseca126f64b2012-03-28 00:13:55 +0100223 QString msg = QLatin1String("Replay finished!");
Zack Rusinf389ae82011-04-10 19:27:28 -0400224
José Fonseca5bba4772012-03-25 12:46:04 +0100225 /*
226 * Construct command line
227 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400228
José Fonseca62997b42011-11-27 15:16:34 +0000229 QString prog;
Zack Rusinf389ae82011-04-10 19:27:28 -0400230 QStringList arguments;
Zack Rusin16ae0362011-04-11 21:30:04 -0400231
José Fonseca889d32c2012-04-23 10:18:28 +0100232 switch (m_api) {
233 case trace::API_GL:
José Fonseca62997b42011-11-27 15:16:34 +0000234 prog = QLatin1String("glretrace");
José Fonseca889d32c2012-04-23 10:18:28 +0100235 break;
236 case trace::API_EGL:
José Fonseca62997b42011-11-27 15:16:34 +0000237 prog = QLatin1String("eglretrace");
José Fonseca889d32c2012-04-23 10:18:28 +0100238 break;
239 case trace::API_DX:
240 case trace::API_D3D7:
241 case trace::API_D3D8:
242 case trace::API_D3D9:
243 case trace::API_D3D10:
244 case trace::API_D3D10_1:
245 case trace::API_D3D11:
246#ifdef Q_OS_WIN
247 prog = QLatin1String("d3dretrace");
248#else
249 prog = QLatin1String("wine");
250 arguments << QLatin1String("d3dretrace.exe");
251#endif
252 break;
253 default:
José Fonseca67964382012-03-27 23:54:30 +0100254 emit finished(QLatin1String("Unsupported API"));
José Fonseca62997b42011-11-27 15:16:34 +0000255 return;
256 }
257
Zack Rusin16ae0362011-04-11 21:30:04 -0400258 if (m_doubleBuffered) {
259 arguments << QLatin1String("-db");
José Fonseca1872d962011-06-01 19:49:13 +0100260 } else {
261 arguments << QLatin1String("-sb");
Zack Rusin16ae0362011-04-11 21:30:04 -0400262 }
263
José Fonseca5bba4772012-03-25 12:46:04 +0100264 if (m_captureState) {
265 arguments << QLatin1String("-D");
266 arguments << QString::number(m_captureCall);
267 } else if (m_captureThumbnails) {
268 arguments << QLatin1String("-s"); // emit snapshots
269 arguments << QLatin1String("-"); // emit to stdout
270 } else if (m_benchmarking) {
271 arguments << QLatin1String("-b");
Zack Rusinf389ae82011-04-10 19:27:28 -0400272 }
273
274 arguments << m_fileName;
275
José Fonseca5bba4772012-03-25 12:46:04 +0100276 /*
277 * Start the process.
278 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400279
José Fonseca5bba4772012-03-25 12:46:04 +0100280 QProcess process;
Zack Rusinf389ae82011-04-10 19:27:28 -0400281
José Fonseca6ea8dee2012-03-25 17:25:24 +0100282 process.start(prog, arguments, QIODevice::ReadOnly);
José Fonseca5bba4772012-03-25 12:46:04 +0100283 if (!process.waitForStarted(-1)) {
284 emit finished(QLatin1String("Could not start process"));
285 return;
286 }
José Fonseca56cd8ac2011-11-24 16:30:49 +0000287
José Fonseca5bba4772012-03-25 12:46:04 +0100288 /*
289 * Process standard output
290 */
291
292 QList<QImage> thumbnails;
293 QVariantMap parsedJson;
294
295 process.setReadChannel(QProcess::StandardOutput);
296 if (process.waitForReadyRead(-1)) {
José Fonseca6ea8dee2012-03-25 17:25:24 +0100297 BlockingIODevice io(&process);
298
José Fonseca5bba4772012-03-25 12:46:04 +0100299 if (m_captureState) {
300 /*
301 * Parse JSON from the output.
302 *
José Fonseca5bba4772012-03-25 12:46:04 +0100303 * XXX: QJSON's scanner is inneficient as it abuses single
304 * character QIODevice::peek (not cheap), instead of maintaining a
305 * lookahead character on its own.
306 */
307
José Fonseca5bba4772012-03-25 12:46:04 +0100308 bool ok = false;
309 QJson::Parser jsonParser;
José Fonseca95b40562012-04-05 20:06:42 +0100310#if 0
José Fonseca6ea8dee2012-03-25 17:25:24 +0100311 parsedJson = jsonParser.parse(&io, &ok).toMap();
José Fonseca95b40562012-04-05 20:06:42 +0100312#else
313 /*
314 * XXX: QJSON expects blocking IO, and it looks like
315 * BlockingIODevice does not work reliably in all cases.
316 */
317 process.waitForFinished(-1);
318 parsedJson = jsonParser.parse(&process, &ok).toMap();
319#endif
José Fonseca5bba4772012-03-25 12:46:04 +0100320 if (!ok) {
321 msg = QLatin1String("failed to parse JSON");
322 }
323 } else if (m_captureThumbnails) {
324 /*
325 * Parse concatenated PNM images from output.
326 */
Dan McCabe66dfdda2012-03-05 17:20:39 -0800327
José Fonseca6ea8dee2012-03-25 17:25:24 +0100328 while (!io.atEnd()) {
José Fonseca5bba4772012-03-25 12:46:04 +0100329 unsigned channels = 0;
330 unsigned width = 0;
331 unsigned height = 0;
332
333 char header[512];
334 qint64 headerSize = 0;
335 int headerLines = 3; // assume no optional comment line
336
337 for (int headerLine = 0; headerLine < headerLines; ++headerLine) {
José Fonseca6ea8dee2012-03-25 17:25:24 +0100338 qint64 headerRead = io.readLine(&header[headerSize], sizeof(header) - headerSize);
José Fonseca5bba4772012-03-25 12:46:04 +0100339
340 // if header actually contains optional comment line, ...
341 if (headerLine == 1 && header[headerSize] == '#') {
342 ++headerLines;
343 }
344
345 headerSize += headerRead;
346 }
347
348 const char *headerEnd = image::readPNMHeader(header, headerSize, &channels, &width, &height);
349
350 // if invalid PNM header was encountered, ...
351 if (header == headerEnd) {
352 qDebug() << "error: invalid snapshot stream encountered";
353 break;
354 }
355
356 // qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height";
357
358 QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888);
359
360 int rowBytes = channels * width;
361 for (int y = 0; y < height; ++y) {
362 unsigned char *scanLine = snapshot.scanLine(y);
José Fonseca6ea8dee2012-03-25 17:25:24 +0100363 qint64 readBytes = io.read((char *) scanLine, rowBytes);
364 Q_ASSERT(readBytes == rowBytes);
José Fonseca5bba4772012-03-25 12:46:04 +0100365 }
366
José Fonsecadc9e9c62012-03-26 10:29:32 +0100367 QImage thumb = thumbnail(snapshot);
368 thumbnails.append(thumb);
Dan McCabe66dfdda2012-03-05 17:20:39 -0800369 }
José Fonseca5bba4772012-03-25 12:46:04 +0100370
371 Q_ASSERT(process.state() != QProcess::Running);
372
José Fonseca56cd8ac2011-11-24 16:30:49 +0000373 } else {
José Fonseca676cd172012-03-24 09:46:24 +0000374 QByteArray output;
José Fonseca5bba4772012-03-25 12:46:04 +0100375 output = process.readAllStandardOutput();
José Fonseca126f64b2012-03-28 00:13:55 +0100376 if (output.length() < 80) {
377 msg = QString::fromUtf8(output);
378 }
José Fonseca56cd8ac2011-11-24 16:30:49 +0000379 }
Zack Rusinf389ae82011-04-10 19:27:28 -0400380 }
381
José Fonseca5bba4772012-03-25 12:46:04 +0100382 /*
383 * Wait for process termination
384 */
385
386 process.waitForFinished(-1);
387
388 if (process.exitStatus() != QProcess::NormalExit) {
389 msg = QLatin1String("Process crashed");
390 } else if (process.exitCode() != 0) {
391 msg = QLatin1String("Process exited with non zero exit code");
392 }
393
394 /*
395 * Parse errors.
396 */
397
Zack Rusin10fd4772011-09-14 01:45:12 -0400398 QList<ApiTraceError> errors;
José Fonseca5bba4772012-03-25 12:46:04 +0100399 process.setReadChannel(QProcess::StandardError);
José Fonseca8fdf56c2012-03-24 10:06:56 +0000400 QRegExp regexp("(^\\d+): +(\\b\\w+\\b): ([^\\r\\n]+)[\\r\\n]*$");
José Fonseca5bba4772012-03-25 12:46:04 +0100401 while (!process.atEnd()) {
402 QString line = process.readLine();
Zack Rusinb39e1c62011-04-19 23:09:26 -0400403 if (regexp.indexIn(line) != -1) {
Zack Rusin10fd4772011-09-14 01:45:12 -0400404 ApiTraceError error;
Zack Rusinb39e1c62011-04-19 23:09:26 -0400405 error.callIndex = regexp.cap(1).toInt();
406 error.type = regexp.cap(2);
407 error.message = regexp.cap(3);
408 errors.append(error);
gregoryf2329b62012-07-06 21:48:59 +0200409 } else if (!errors.isEmpty()) {
410 // Probably a multiligne message
411 ApiTraceError &previous = errors.last();
412 if (line.endsWith("\n")) {
413 line.chop(1);
414 }
415 previous.message.append('\n');
416 previous.message.append(line);
Zack Rusinb39e1c62011-04-19 23:09:26 -0400417 }
418 }
José Fonseca5bba4772012-03-25 12:46:04 +0100419
420 /*
421 * Emit signals
422 */
423
424 if (m_captureState) {
425 ApiTraceState *state = new ApiTraceState(parsedJson);
426 emit foundState(state);
427 msg = QLatin1String("State fetched.");
428 }
429
430 if (m_captureThumbnails && !thumbnails.isEmpty()) {
431 emit foundThumbnails(thumbnails);
432 }
433
Zack Rusinb39e1c62011-04-19 23:09:26 -0400434 if (!errors.isEmpty()) {
435 emit retraceErrors(errors);
436 }
José Fonseca5bba4772012-03-25 12:46:04 +0100437
Zack Rusinf389ae82011-04-10 19:27:28 -0400438 emit finished(msg);
Zack Rusin3acde362011-04-06 01:11:55 -0400439}
440
Zack Rusin3acde362011-04-06 01:11:55 -0400441#include "retracer.moc"