blob: ca9da9a01c0f7d23c7a15fcd1038b7eb540dfd65 [file] [log] [blame]
Zack Rusin3acde362011-04-06 01:11:55 -04001#include "retracer.h"
José Fonseca5bba4772012-03-25 12:46:04 +01002#include <iostream>
Zack Rusin3acde362011-04-06 01:11:55 -04003
Zack Rusinf389ae82011-04-10 19:27:28 -04004#include "apitracecall.h"
5
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é Fonseca5bba4772012-03-25 12:46:04 +010015Q_DECLARE_METATYPE(QList<ApiTraceError>);
16
Zack Rusin3acde362011-04-06 01:11:55 -040017Retracer::Retracer(QObject *parent)
Zack Rusinf389ae82011-04-10 19:27:28 -040018 : QThread(parent),
Zack Rusin404a1ef2011-04-19 23:49:56 -040019 m_benchmarking(false),
Zack Rusin3acde362011-04-06 01:11:55 -040020 m_doubleBuffered(true),
21 m_captureState(false),
Zack Rusinf389ae82011-04-10 19:27:28 -040022 m_captureCall(0)
Zack Rusin3acde362011-04-06 01:11:55 -040023{
José Fonseca5bba4772012-03-25 12:46:04 +010024 qRegisterMetaType<QList<ApiTraceError> >();
25
Zack Rusinf389ae82011-04-10 19:27:28 -040026#ifdef Q_OS_WIN
27 QString format = QLatin1String("%1;");
28#else
29 QString format = QLatin1String("%1:");
30#endif
José Fonseca27440922011-11-01 08:27:12 +000031 QString buildPath = format.arg(APITRACE_BINARY_DIR);
Zack Rusinf389ae82011-04-10 19:27:28 -040032 m_processEnvironment = QProcessEnvironment::systemEnvironment();
33 m_processEnvironment.insert("PATH", buildPath +
34 m_processEnvironment.value("PATH"));
35
36 qputenv("PATH",
37 m_processEnvironment.value("PATH").toLatin1());
Zack Rusin3acde362011-04-06 01:11:55 -040038}
39
40QString Retracer::fileName() const
41{
42 return m_fileName;
43}
44
45void Retracer::setFileName(const QString &name)
46{
47 m_fileName = name;
48}
49
José Fonseca62997b42011-11-27 15:16:34 +000050void Retracer::setAPI(trace::API api)
51{
52 m_api = api;
53}
54
Zack Rusin3acde362011-04-06 01:11:55 -040055bool Retracer::isBenchmarking() const
56{
57 return m_benchmarking;
58}
59
60void Retracer::setBenchmarking(bool bench)
61{
62 m_benchmarking = bench;
63}
64
65bool Retracer::isDoubleBuffered() const
66{
67 return m_doubleBuffered;
68}
69
70void Retracer::setDoubleBuffered(bool db)
71{
72 m_doubleBuffered = db;
73}
74
Zack Rusin3acde362011-04-06 01:11:55 -040075void Retracer::setCaptureAtCallNumber(qlonglong num)
76{
77 m_captureCall = num;
78}
79
80qlonglong Retracer::captureAtCallNumber() const
81{
82 return m_captureCall;
83}
84
85bool Retracer::captureState() const
86{
87 return m_captureState;
88}
89
90void Retracer::setCaptureState(bool enable)
91{
92 m_captureState = enable;
93}
94
Dan McCabe66dfdda2012-03-05 17:20:39 -080095bool Retracer::captureThumbnails() const
96{
97 return m_captureThumbnails;
98}
99
100void Retracer::setCaptureThumbnails(bool enable)
101{
102 m_captureThumbnails = enable;
103}
104
Zack Rusinf389ae82011-04-10 19:27:28 -0400105
José Fonseca5bba4772012-03-25 12:46:04 +0100106/**
107 * Starting point for the retracing thread.
108 *
109 * Overrides QThread::run().
110 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400111void Retracer::run()
112{
José Fonseca5bba4772012-03-25 12:46:04 +0100113 QString msg;
Zack Rusinf389ae82011-04-10 19:27:28 -0400114
José Fonseca5bba4772012-03-25 12:46:04 +0100115 /*
116 * Construct command line
117 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400118
José Fonseca62997b42011-11-27 15:16:34 +0000119 QString prog;
Zack Rusinf389ae82011-04-10 19:27:28 -0400120 QStringList arguments;
Zack Rusin16ae0362011-04-11 21:30:04 -0400121
José Fonseca62997b42011-11-27 15:16:34 +0000122 if (m_api == trace::API_GL) {
123 prog = QLatin1String("glretrace");
124 } else if (m_api == trace::API_EGL) {
125 prog = QLatin1String("eglretrace");
126 } else {
José Fonseca5bba4772012-03-25 12:46:04 +0100127 Q_ASSERT(0);
José Fonseca62997b42011-11-27 15:16:34 +0000128 return;
129 }
130
Zack Rusin16ae0362011-04-11 21:30:04 -0400131 if (m_doubleBuffered) {
132 arguments << QLatin1String("-db");
José Fonseca1872d962011-06-01 19:49:13 +0100133 } else {
134 arguments << QLatin1String("-sb");
Zack Rusin16ae0362011-04-11 21:30:04 -0400135 }
136
José Fonseca5bba4772012-03-25 12:46:04 +0100137 if (m_captureState) {
138 arguments << QLatin1String("-D");
139 arguments << QString::number(m_captureCall);
140 } else if (m_captureThumbnails) {
141 arguments << QLatin1String("-s"); // emit snapshots
142 arguments << QLatin1String("-"); // emit to stdout
143 } else if (m_benchmarking) {
144 arguments << QLatin1String("-b");
Zack Rusinf389ae82011-04-10 19:27:28 -0400145 }
146
147 arguments << m_fileName;
148
José Fonseca5bba4772012-03-25 12:46:04 +0100149 /*
150 * Start the process.
151 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400152
José Fonseca5bba4772012-03-25 12:46:04 +0100153 QProcess process;
Zack Rusinf389ae82011-04-10 19:27:28 -0400154
José Fonseca5bba4772012-03-25 12:46:04 +0100155 process.start(prog, arguments);
156 if (!process.waitForStarted(-1)) {
157 emit finished(QLatin1String("Could not start process"));
158 return;
159 }
José Fonseca56cd8ac2011-11-24 16:30:49 +0000160
José Fonseca5bba4772012-03-25 12:46:04 +0100161 /*
162 * Process standard output
163 */
164
165 QList<QImage> thumbnails;
166 QVariantMap parsedJson;
167
168 process.setReadChannel(QProcess::StandardOutput);
169 if (process.waitForReadyRead(-1)) {
170 if (m_captureState) {
171 /*
172 * Parse JSON from the output.
173 *
174 * XXX: QJSON does not wait for QIODevice::waitForReadyRead so we
175 * need to buffer all stdout.
176 *
177 * XXX: QJSON's scanner is inneficient as it abuses single
178 * character QIODevice::peek (not cheap), instead of maintaining a
179 * lookahead character on its own.
180 */
181
182 if (!process.waitForFinished(-1)) {
183 return;
Dan McCabe66dfdda2012-03-05 17:20:39 -0800184 }
Dan McCabe66dfdda2012-03-05 17:20:39 -0800185
José Fonseca5bba4772012-03-25 12:46:04 +0100186 bool ok = false;
187 QJson::Parser jsonParser;
188 parsedJson = jsonParser.parse(&process, &ok).toMap();
189 if (!ok) {
190 msg = QLatin1String("failed to parse JSON");
191 }
192 } else if (m_captureThumbnails) {
193 /*
194 * Parse concatenated PNM images from output.
195 */
Dan McCabe66dfdda2012-03-05 17:20:39 -0800196
José Fonseca5bba4772012-03-25 12:46:04 +0100197 while (true) {
198 /*
199 * QProcess::atEnd() documentation is wrong -- it will return
200 * true even when the process is running --, so try to handle
201 * that here.
202 */
203 if (process.atEnd()) {
204 if (process.state() == QProcess::Running) {
205 if (!process.waitForReadyRead(-1)) {
206 break;
Dan McCabe66dfdda2012-03-05 17:20:39 -0800207 }
Dan McCabe66dfdda2012-03-05 17:20:39 -0800208 }
Dan McCabe66dfdda2012-03-05 17:20:39 -0800209 }
210
José Fonseca5bba4772012-03-25 12:46:04 +0100211 unsigned channels = 0;
212 unsigned width = 0;
213 unsigned height = 0;
214
215 char header[512];
216 qint64 headerSize = 0;
217 int headerLines = 3; // assume no optional comment line
218
219 for (int headerLine = 0; headerLine < headerLines; ++headerLine) {
220 while (!process.canReadLine()) {
221 if (!process.waitForReadyRead(-1)) {
222 qDebug() << "QProcess::waitForReadyRead failed";
223 break;
224 }
225 }
226
227 qint64 headerRead = process.readLine(&header[headerSize], sizeof(header) - headerSize);
228
229 // if header actually contains optional comment line, ...
230 if (headerLine == 1 && header[headerSize] == '#') {
231 ++headerLines;
232 }
233
234 headerSize += headerRead;
235 }
236
237 const char *headerEnd = image::readPNMHeader(header, headerSize, &channels, &width, &height);
238
239 // if invalid PNM header was encountered, ...
240 if (header == headerEnd) {
241 qDebug() << "error: invalid snapshot stream encountered";
242 break;
243 }
244
245 // qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height";
246
247 QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888);
248
249 int rowBytes = channels * width;
250 for (int y = 0; y < height; ++y) {
251 unsigned char *scanLine = snapshot.scanLine(y);
252
253 while (process.bytesAvailable() < rowBytes) {
254 if (!process.waitForReadyRead(-1)) {
255 qDebug() << "QProcess::waitForReadyRead failed";
256 break;
257 }
258 }
259
260 qint64 read = process.read((char *) scanLine, rowBytes);
261 Q_ASSERT(read == rowBytes);
262 }
263
264 QImage thumbnail = snapshot.scaled(16, 16, Qt::KeepAspectRatio, Qt::FastTransformation);
265 thumbnails.append(thumbnail);
Dan McCabe66dfdda2012-03-05 17:20:39 -0800266 }
José Fonseca5bba4772012-03-25 12:46:04 +0100267
268 Q_ASSERT(process.state() != QProcess::Running);
269
José Fonseca56cd8ac2011-11-24 16:30:49 +0000270 } else {
José Fonseca676cd172012-03-24 09:46:24 +0000271 QByteArray output;
José Fonseca5bba4772012-03-25 12:46:04 +0100272 output = process.readAllStandardOutput();
José Fonseca56cd8ac2011-11-24 16:30:49 +0000273 msg = QString::fromUtf8(output);
274 }
Zack Rusinf389ae82011-04-10 19:27:28 -0400275 }
276
José Fonseca5bba4772012-03-25 12:46:04 +0100277 /*
278 * Wait for process termination
279 */
280
281 process.waitForFinished(-1);
282
283 if (process.exitStatus() != QProcess::NormalExit) {
284 msg = QLatin1String("Process crashed");
285 } else if (process.exitCode() != 0) {
286 msg = QLatin1String("Process exited with non zero exit code");
287 }
288
289 /*
290 * Parse errors.
291 */
292
Zack Rusin10fd4772011-09-14 01:45:12 -0400293 QList<ApiTraceError> errors;
José Fonseca5bba4772012-03-25 12:46:04 +0100294 process.setReadChannel(QProcess::StandardError);
José Fonseca8fdf56c2012-03-24 10:06:56 +0000295 QRegExp regexp("(^\\d+): +(\\b\\w+\\b): ([^\\r\\n]+)[\\r\\n]*$");
José Fonseca5bba4772012-03-25 12:46:04 +0100296 while (!process.atEnd()) {
297 QString line = process.readLine();
Zack Rusinb39e1c62011-04-19 23:09:26 -0400298 if (regexp.indexIn(line) != -1) {
Zack Rusin10fd4772011-09-14 01:45:12 -0400299 ApiTraceError error;
Zack Rusinb39e1c62011-04-19 23:09:26 -0400300 error.callIndex = regexp.cap(1).toInt();
301 error.type = regexp.cap(2);
302 error.message = regexp.cap(3);
303 errors.append(error);
304 }
305 }
José Fonseca5bba4772012-03-25 12:46:04 +0100306
307 /*
308 * Emit signals
309 */
310
311 if (m_captureState) {
312 ApiTraceState *state = new ApiTraceState(parsedJson);
313 emit foundState(state);
314 msg = QLatin1String("State fetched.");
315 }
316
317 if (m_captureThumbnails && !thumbnails.isEmpty()) {
318 emit foundThumbnails(thumbnails);
319 }
320
Zack Rusinb39e1c62011-04-19 23:09:26 -0400321 if (!errors.isEmpty()) {
322 emit retraceErrors(errors);
323 }
José Fonseca5bba4772012-03-25 12:46:04 +0100324
Zack Rusinf389ae82011-04-10 19:27:28 -0400325 emit finished(msg);
Zack Rusin3acde362011-04-06 01:11:55 -0400326}
327
Zack Rusin3acde362011-04-06 01:11:55 -0400328#include "retracer.moc"