+ QString msg = QLatin1String("Replay finished!");
+
+ /*
+ * Construct command line
+ */
+
+ QString prog;
+ QStringList arguments;
+
+ switch (m_api) {
+ case trace::API_GL:
+ prog = QLatin1String("glretrace");
+ break;
+ case trace::API_EGL:
+ prog = QLatin1String("eglretrace");
+ break;
+ case trace::API_DX:
+ case trace::API_D3D7:
+ case trace::API_D3D8:
+ case trace::API_D3D9:
+ case trace::API_D3D10:
+ case trace::API_D3D10_1:
+ case trace::API_D3D11:
+#ifdef Q_OS_WIN
+ prog = QLatin1String("d3dretrace");
+#else
+ prog = QLatin1String("wine");
+ arguments << QLatin1String("d3dretrace.exe");
+#endif
+ break;
+ default:
+ emit finished(QLatin1String("Unsupported API"));
+ return;
+ }
+
+ if (m_captureState) {
+ arguments << QLatin1String("-D");
+ arguments << QString::number(m_captureCall);
+ } else if (m_captureThumbnails) {
+ arguments << QLatin1String("-s"); // emit snapshots
+ arguments << QLatin1String("-"); // emit to stdout
+ } else if (isProfiling()) {
+ if (m_profileGpu) {
+ arguments << QLatin1String("--pgpu");
+ }
+
+ if (m_profileCpu) {
+ arguments << QLatin1String("--pcpu");
+ }
+
+ if (m_profilePixels) {
+ arguments << QLatin1String("--ppd");
+ }
+ } else {
+ if (m_doubleBuffered) {
+ arguments << QLatin1String("--db");
+ } else {
+ arguments << QLatin1String("--sb");
+ }
+
+ if (m_benchmarking) {
+ arguments << QLatin1String("-b");
+ }
+ }
+
+ arguments << m_fileName;
+
+ /*
+ * Start the process.
+ */
+
+ QProcess process;
+
+ process.start(prog, arguments, QIODevice::ReadOnly);
+ if (!process.waitForStarted(-1)) {
+ emit finished(QLatin1String("Could not start process"));
+ return;
+ }
+
+ /*
+ * Process standard output
+ */
+
+ QList<QImage> thumbnails;
+ QVariantMap parsedJson;
+ trace::Profile* profile = NULL;
+
+ process.setReadChannel(QProcess::StandardOutput);
+ if (process.waitForReadyRead(-1)) {
+ BlockingIODevice io(&process);
+
+ if (m_captureState) {
+ /*
+ * Parse JSON from the output.
+ *
+ * XXX: QJSON's scanner is inneficient as it abuses single
+ * character QIODevice::peek (not cheap), instead of maintaining a
+ * lookahead character on its own.
+ */
+
+ bool ok = false;
+ QJson::Parser jsonParser;
+
+ // Allow Nan/Infinity
+ jsonParser.allowSpecialNumbers(true);
+#if 0
+ parsedJson = jsonParser.parse(&io, &ok).toMap();
+#else
+ /*
+ * XXX: QJSON expects blocking IO, and it looks like
+ * BlockingIODevice does not work reliably in all cases.
+ */
+ process.waitForFinished(-1);
+ parsedJson = jsonParser.parse(&process, &ok).toMap();
+#endif
+ if (!ok) {
+ msg = QLatin1String("failed to parse JSON");
+ }
+ } else if (m_captureThumbnails) {
+ /*
+ * Parse concatenated PNM images from output.
+ */
+
+ while (!io.atEnd()) {
+ unsigned channels = 0;
+ unsigned width = 0;
+ unsigned height = 0;
+
+ char header[512];
+ qint64 headerSize = 0;
+ int headerLines = 3; // assume no optional comment line
+
+ for (int headerLine = 0; headerLine < headerLines; ++headerLine) {
+ qint64 headerRead = io.readLine(&header[headerSize], sizeof(header) - headerSize);
+
+ // if header actually contains optional comment line, ...
+ if (headerLine == 1 && header[headerSize] == '#') {
+ ++headerLines;
+ }
+
+ headerSize += headerRead;
+ }
+
+ const char *headerEnd = image::readPNMHeader(header, headerSize, &channels, &width, &height);
+
+ // if invalid PNM header was encountered, ...
+ if (header == headerEnd) {
+ qDebug() << "error: invalid snapshot stream encountered";
+ break;
+ }
+
+ // qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height";
+
+ QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888);
+
+ int rowBytes = channels * width;
+ for (int y = 0; y < height; ++y) {
+ unsigned char *scanLine = snapshot.scanLine(y);
+ qint64 readBytes = io.read((char *) scanLine, rowBytes);
+ Q_ASSERT(readBytes == rowBytes);
+ (void)readBytes;
+ }
+
+ QImage thumb = thumbnail(snapshot);
+ thumbnails.append(thumb);
+ }
+
+ Q_ASSERT(process.state() != QProcess::Running);
+ } else if (isProfiling()) {
+ profile = new trace::Profile();
+
+ while (!io.atEnd()) {
+ char line[256];
+ qint64 lineLength;
+
+ lineLength = io.readLine(line, 256);
+
+ if (lineLength == -1)
+ break;
+
+ trace::Profiler::parseLine(line, profile);
+ }
+ } else {
+ QByteArray output;
+ output = process.readAllStandardOutput();
+ if (output.length() < 80) {
+ msg = QString::fromUtf8(output);
+ }
+ }
+ }
+
+ /*
+ * Wait for process termination
+ */
+
+ process.waitForFinished(-1);
+
+ if (process.exitStatus() != QProcess::NormalExit) {
+ msg = QLatin1String("Process crashed");
+ } else if (process.exitCode() != 0) {
+ msg = QLatin1String("Process exited with non zero exit code");
+ }
+
+ /*
+ * Parse errors.
+ */
+
+ QList<ApiTraceError> errors;
+ process.setReadChannel(QProcess::StandardError);
+ QRegExp regexp("(^\\d+): +(\\b\\w+\\b): ([^\\r\\n]+)[\\r\\n]*$");
+ while (!process.atEnd()) {
+ QString line = process.readLine();
+ if (regexp.indexIn(line) != -1) {
+ ApiTraceError error;
+ error.callIndex = regexp.cap(1).toInt();
+ error.type = regexp.cap(2);
+ error.message = regexp.cap(3);
+ errors.append(error);
+ } else if (!errors.isEmpty()) {
+ // Probably a multiligne message
+ ApiTraceError &previous = errors.last();
+ if (line.endsWith("\n")) {
+ line.chop(1);
+ }
+ previous.message.append('\n');
+ previous.message.append(line);
+ }
+ }
+
+ /*
+ * Emit signals
+ */
+
+ if (m_captureState) {
+ ApiTraceState *state = new ApiTraceState(parsedJson);
+ emit foundState(state);
+ msg = QLatin1String("State fetched.");
+ }
+
+ if (m_captureThumbnails && !thumbnails.isEmpty()) {
+ emit foundThumbnails(thumbnails);
+ }
+
+ if (isProfiling() && profile) {
+ emit foundProfile(profile);
+ }
+
+ if (!errors.isEmpty()) {
+ emit retraceErrors(errors);
+ }