X-Git-Url: https://git.cworth.org/git?a=blobdiff_plain;f=gui%2Fretracer.cpp;h=2928ed63f321f31e34667e2448e1b8f765f2fdbf;hb=ebe01ce9942e570f23b40793e97cf02b6f3616ee;hp=251028ce5ab316066dcfd8baac94a6376a91eb5b;hpb=ae2b4d32ed56e3ac193cc7205aeb58082c448ce8;p=apitrace diff --git a/gui/retracer.cpp b/gui/retracer.cpp index 251028c..2928ed6 100644 --- a/gui/retracer.cpp +++ b/gui/retracer.cpp @@ -1,31 +1,142 @@ #include "retracer.h" #include "apitracecall.h" +#include "thumbnail.h" + +#include "image/image.hpp" + +#include "trace_profiler.hpp" #include #include +#include +#include #include +/** + * Wrapper around a QProcess which enforces IO to block . + * + * Several QIODevice users (notably QJSON) expect blocking semantics, e.g., + * they expect that QIODevice::read() will blocked until the requested ammount + * of bytes is read or end of file is reached. But by default QProcess, does + * not block. And passing QIODevice::Unbuffered mitigates but does not fully + * address the problem either. + * + * This class wraps around QProcess, providing QIODevice interface, while + * ensuring that all reads block. + * + * This class also works around a bug in QProcess::atEnd() implementation. + * + * See also: + * - http://qt-project.org/wiki/Simple_Crypt_IO_Device + * - http://qt-project.org/wiki/Custom_IO_Device + */ +class BlockingIODevice : public QIODevice +{ + /* We don't use the Q_OBJECT in this class given we don't declare any + * signals and slots or use any other services provided by Qt's meta-object + * system. */ +public: + BlockingIODevice(QProcess * io); + bool isSequential() const; + bool atEnd() const; + bool waitForReadyRead(int msecs = -1); + +protected: + qint64 readData(char * data, qint64 maxSize); + qint64 writeData(const char * data, qint64 maxSize); + +private: + QProcess *m_device; +}; + +BlockingIODevice::BlockingIODevice(QProcess * io) : + m_device(io) +{ + /* + * We pass QIODevice::Unbuffered to prevent the base QIODevice class to do + * its own buffering on top of the overridden readData() method. + * + * The only buffering used will be to satisfy QIODevice::peek() and + * QIODevice::ungetChar(). + */ + setOpenMode(ReadOnly | Unbuffered); +} + +bool BlockingIODevice::isSequential() const +{ + return true; +} + +bool BlockingIODevice::atEnd() const +{ + /* + * XXX: QProcess::atEnd() documentation is wrong -- it will return true + * even when the process is running --, so we try to workaround that here. + */ + if (m_device->atEnd()) { + if (m_device->state() == QProcess::Running) { + if (!m_device->waitForReadyRead(-1)) { + return true; + } + } + } + return false; +} + +bool BlockingIODevice::waitForReadyRead(int msecs) +{ + Q_UNUSED(msecs); + return true; +} + +qint64 BlockingIODevice::readData(char * data, qint64 maxSize) +{ + qint64 bytesToRead = maxSize; + qint64 readSoFar = 0; + do { + qint64 chunkSize = m_device->read(data + readSoFar, bytesToRead); + if (chunkSize < 0) { + if (readSoFar) { + return readSoFar; + } else { + return chunkSize; + } + } + Q_ASSERT(chunkSize <= bytesToRead); + bytesToRead -= chunkSize; + readSoFar += chunkSize; + if (bytesToRead) { + if (!m_device->waitForReadyRead(-1)) { + qDebug() << "waitForReadyRead failed\n"; + break; + } + } + } while(bytesToRead); + + return readSoFar; +} + +qint64 BlockingIODevice::writeData(const char * data, qint64 maxSize) +{ + Q_ASSERT(false); + return -1; +} + +Q_DECLARE_METATYPE(QList); + Retracer::Retracer(QObject *parent) : QThread(parent), m_benchmarking(false), m_doubleBuffered(true), m_captureState(false), - m_captureCall(0) + m_captureCall(0), + m_profileGpu(false), + m_profileCpu(false), + m_profilePixels(false) { -#ifdef Q_OS_WIN - QString format = QLatin1String("%1;"); -#else - QString format = QLatin1String("%1:"); -#endif - QString buildPath = format.arg(BUILD_DIR); - m_processEnvironment = QProcessEnvironment::systemEnvironment(); - m_processEnvironment.insert("PATH", buildPath + - m_processEnvironment.value("PATH")); - - qputenv("PATH", - m_processEnvironment.value("PATH").toLatin1()); + qRegisterMetaType >(); } QString Retracer::fileName() const @@ -38,6 +149,11 @@ void Retracer::setFileName(const QString &name) m_fileName = name; } +void Retracer::setAPI(trace::API api) +{ + m_api = api; +} + bool Retracer::isBenchmarking() const { return m_benchmarking; @@ -58,6 +174,33 @@ void Retracer::setDoubleBuffered(bool db) m_doubleBuffered = db; } +bool Retracer::isProfilingGpu() const +{ + return m_profileGpu; +} + +bool Retracer::isProfilingCpu() const +{ + return m_profileCpu; +} + +bool Retracer::isProfilingPixels() const +{ + return m_profilePixels; +} + +bool Retracer::isProfiling() const +{ + return m_profileGpu || m_profileCpu || m_profilePixels; +} + +void Retracer::setProfiling(bool gpu, bool cpu, bool pixels) +{ + m_profileGpu = gpu; + m_profileCpu = cpu; + m_profilePixels = pixels; +} + void Retracer::setCaptureAtCallNumber(qlonglong num) { m_captureCall = num; @@ -78,58 +221,81 @@ void Retracer::setCaptureState(bool enable) m_captureState = enable; } - -void Retracer::run() +bool Retracer::captureThumbnails() const { - RetraceProcess *retrace = new RetraceProcess(); - retrace->process()->setProcessEnvironment(m_processEnvironment); - - retrace->setFileName(m_fileName); - retrace->setBenchmarking(m_benchmarking); - retrace->setDoubleBuffered(m_doubleBuffered); - retrace->setCaptureState(m_captureState); - retrace->setCaptureAtCallNumber(m_captureCall); - - connect(retrace, SIGNAL(finished(const QString&)), - this, SLOT(cleanup())); - connect(retrace, SIGNAL(error(const QString&)), - this, SLOT(cleanup())); - connect(retrace, SIGNAL(finished(const QString&)), - this, SIGNAL(finished(const QString&))); - connect(retrace, SIGNAL(error(const QString&)), - this, SIGNAL(error(const QString&))); - connect(retrace, SIGNAL(foundState(ApiTraceState*)), - this, SIGNAL(foundState(ApiTraceState*))); - connect(retrace, SIGNAL(retraceErrors(const QList&)), - this, SIGNAL(retraceErrors(const QList&))); - - retrace->start(); - - exec(); - - /* means we need to kill the process */ - if (retrace->process()->state() != QProcess::NotRunning) { - retrace->terminate(); - } - - delete retrace; + return m_captureThumbnails; } +void Retracer::setCaptureThumbnails(bool enable) +{ + m_captureThumbnails = enable; +} -void RetraceProcess::start() +/** + * Starting point for the retracing thread. + * + * Overrides QThread::run(). + */ +void Retracer::run() { + QString msg = QLatin1String("Replay finished!"); + + /* + * Construct command line + */ + + QString prog; QStringList arguments; - if (m_doubleBuffered) { - arguments << QLatin1String("-db"); - } else { - arguments << QLatin1String("-sb"); + 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_DXGI: +#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"); } @@ -137,145 +303,191 @@ void RetraceProcess::start() arguments << m_fileName; - m_process->start(QLatin1String("glretrace"), arguments); -} + /* + * Start the process. + */ + QProcess process; -void RetraceProcess::replayFinished() -{ - QByteArray output = m_process->readAllStandardOutput(); - QString msg; - QString errStr = m_process->readAllStandardError(); + process.start(prog, arguments, QIODevice::ReadOnly); + if (!process.waitForStarted(-1)) { + emit finished(QLatin1String("Could not start process")); + return; + } + + /* + * Process standard output + */ + QList 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 - qDebug()<<"Process finished = "; - qDebug()<<"\terr = "< errors; - QRegExp regexp("(^\\d+): +(\\b\\w+\\b): (.+$)"); - foreach(QString line, errorLines) { + 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); } } - if (!errors.isEmpty()) { - emit retraceErrors(errors); - } - emit finished(msg); -} - -void RetraceProcess::replayError(QProcess::ProcessError err) -{ - qDebug()<<"Process error = "<); -RetraceProcess::RetraceProcess(QObject *parent) - : QObject(parent) -{ - m_process = new QProcess(this); - m_jsonParser = new QJson::Parser(); - - qRegisterMetaType >(); - - connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), - this, SLOT(replayFinished())); - connect(m_process, SIGNAL(error(QProcess::ProcessError)), - this, SLOT(replayError(QProcess::ProcessError))); -} - -QProcess * RetraceProcess::process() const -{ - return m_process; -} - -QString RetraceProcess::fileName() const -{ - return m_fileName; -} - -void RetraceProcess::setFileName(const QString &name) -{ - m_fileName = name; -} - -bool RetraceProcess::isBenchmarking() const -{ - return m_benchmarking; -} - -void RetraceProcess::setBenchmarking(bool bench) -{ - m_benchmarking = bench; -} - -bool RetraceProcess::isDoubleBuffered() const -{ - return m_doubleBuffered; -} - -void RetraceProcess::setDoubleBuffered(bool db) -{ - m_doubleBuffered = db; -} - -void RetraceProcess::setCaptureAtCallNumber(qlonglong num) -{ - m_captureCall = num; -} -qlonglong RetraceProcess::captureAtCallNumber() const -{ - return m_captureCall; -} + /* + * Emit signals + */ -bool RetraceProcess::captureState() const -{ - return m_captureState; -} + if (m_captureState) { + ApiTraceState *state = new ApiTraceState(parsedJson); + emit foundState(state); + msg = QLatin1String("State fetched."); + } -void RetraceProcess::setCaptureState(bool enable) -{ - m_captureState = enable; -} + if (m_captureThumbnails && !thumbnails.isEmpty()) { + emit foundThumbnails(thumbnails); + } -void RetraceProcess::terminate() -{ - if (m_process) { - m_process->terminate(); - emit finished(tr("Process terminated.")); + if (isProfiling() && profile) { + emit foundProfile(profile); } -} -void Retracer::cleanup() -{ - quit(); -} + if (!errors.isEmpty()) { + emit retraceErrors(errors); + } -RetraceProcess::~RetraceProcess() -{ - delete m_jsonParser; + emit finished(msg); } #include "retracer.moc"