From 026a8ec802361de4080b738db2bf6db85e047f76 Mon Sep 17 00:00:00 2001 From: Johan Klokkhammer Helsing Date: Tue, 10 Dec 2019 13:18:21 +0100 Subject: Move QOpenGLTimerQuery to from QtGui to QtOpenGL Task-number: QTBUG-74409 Change-Id: Ic8ef0a814700b2ed76b661d560907ad498334231 Reviewed-by: Laszlo Agocs --- src/opengl/opengl.pro | 8 + src/opengl/qopenglqueryhelper_p.h | 186 ++++++++ src/opengl/qopengltimerquery.cpp | 880 ++++++++++++++++++++++++++++++++++++++ src/opengl/qopengltimerquery.h | 116 +++++ 4 files changed, 1190 insertions(+) create mode 100644 src/opengl/qopenglqueryhelper_p.h create mode 100644 src/opengl/qopengltimerquery.cpp create mode 100644 src/opengl/qopengltimerquery.h (limited to 'src/opengl') diff --git a/src/opengl/opengl.pro b/src/opengl/opengl.pro index b9d0c6213d..820e46ed79 100644 --- a/src/opengl/opengl.pro +++ b/src/opengl/opengl.pro @@ -15,4 +15,12 @@ HEADERS += \ SOURCES += \ qopengldebug.cpp +!qtConfig(opengles2) { + HEADERS += \ + qopenglqueryhelper_p.h \ + qopengltimerquery.h + + SOURCES += qopengltimerquery.cpp +} + load(qt_module) diff --git a/src/opengl/qopenglqueryhelper_p.h b/src/opengl/qopenglqueryhelper_p.h new file mode 100644 index 0000000000..f3ed997f98 --- /dev/null +++ b/src/opengl/qopenglqueryhelper_p.h @@ -0,0 +1,186 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtOpenGL module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QOPENGLQUERYHELPER_P_H +#define QOPENGLQUERYHELPER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#if !defined(QT_OPENGL_ES_2) + +#include + +QT_BEGIN_NAMESPACE + +// Helper class used by QOpenGLTimerQuery and later will be used by +// QOpenGLOcclusionQuery +class QOpenGLQueryHelper +{ +public: + QOpenGLQueryHelper(QOpenGLContext *context) + : GetQueryObjectuiv(nullptr), + GetQueryObjectiv(nullptr), + GetQueryiv(nullptr), + EndQuery(nullptr), + BeginQuery(nullptr), + IsQuery(nullptr), + DeleteQueries(nullptr), + GenQueries(nullptr), + GetInteger64v(nullptr), + GetQueryObjectui64v(nullptr), + GetQueryObjecti64v(nullptr), + QueryCounter(nullptr) + { + Q_ASSERT(context); + + // Core in OpenGL >=1.5 + GetQueryObjectuiv = reinterpret_cast(context->getProcAddress("glGetQueryObjectuiv")); + GetQueryObjectiv = reinterpret_cast(context->getProcAddress("glGetQueryObjectiv")); + GetQueryiv = reinterpret_cast(context->getProcAddress("glGetQueryiv")); + EndQuery = reinterpret_cast(context->getProcAddress("glEndQuery")); + BeginQuery = reinterpret_cast(context->getProcAddress("glBeginQuery")); + IsQuery = reinterpret_cast(context->getProcAddress("glIsQuery")); + DeleteQueries = reinterpret_cast(context->getProcAddress("glDeleteQueries")); + GenQueries = reinterpret_cast(context->getProcAddress("glGenQueries")); + + // Core in OpenGL >=3.2 + GetInteger64v = reinterpret_cast(context->getProcAddress("glGetInteger64v")); + + // Core in OpenGL >=3.3 / ARB_timer_query + GetQueryObjectui64v = reinterpret_cast(context->getProcAddress("glGetQueryObjectui64v")); + GetQueryObjecti64v = reinterpret_cast(context->getProcAddress("glGetQueryObjecti64v")); + QueryCounter = reinterpret_cast(context->getProcAddress("glQueryCounter")); + } + + inline void glGetQueryObjectuiv(GLuint id, GLenum pname, GLuint *params) + { + GetQueryObjectuiv(id, pname, params); + } + + inline void glGetQueryObjectiv(GLuint id, GLenum pname, GLint *params) + { + GetQueryObjectiv(id, pname, params); + } + + inline void glGetQueryiv(GLenum target, GLenum pname, GLint *params) + { + GetQueryiv(target, pname, params); + } + + inline void glEndQuery(GLenum target) + { + EndQuery(target); + } + + inline void glBeginQuery(GLenum target, GLuint id) + { + BeginQuery(target, id); + } + + inline GLboolean glIsQuery(GLuint id) + { + return IsQuery(id); + } + + inline void glDeleteQueries(GLsizei n, const GLuint *ids) + { + DeleteQueries(n, ids); + } + + inline void glGenQueries(GLsizei n, GLuint *ids) + { + GenQueries(n, ids); + } + + inline void glGetInteger64v(GLenum pname, GLint64 *params) + { + GetInteger64v(pname, params); + } + + inline void glGetQueryObjectui64v(GLuint id, GLenum pname, GLuint64 *params) + { + GetQueryObjectui64v(id, pname, params); + } + + inline void glGetQueryObjecti64v(GLuint id, GLenum pname, GLint64 *params) + { + GetQueryObjecti64v(id, pname, params); + } + + inline void glQueryCounter(GLuint id, GLenum target) + { + QueryCounter(id, target); + } + +private: + // Core in OpenGL >=1.5 + void (QOPENGLF_APIENTRYP GetQueryObjectuiv)(GLuint id, GLenum pname, GLuint *params); + void (QOPENGLF_APIENTRYP GetQueryObjectiv)(GLuint id, GLenum pname, GLint *params); + void (QOPENGLF_APIENTRYP GetQueryiv)(GLenum target, GLenum pname, GLint *params); + void (QOPENGLF_APIENTRYP EndQuery)(GLenum target); + void (QOPENGLF_APIENTRYP BeginQuery)(GLenum target, GLuint id); + GLboolean (QOPENGLF_APIENTRYP IsQuery)(GLuint id); + void (QOPENGLF_APIENTRYP DeleteQueries)(GLsizei n, const GLuint *ids); + void (QOPENGLF_APIENTRYP GenQueries)(GLsizei n, GLuint *ids); + + // Core in OpenGL >=3.2 + void (QOPENGLF_APIENTRYP GetInteger64v)(GLenum pname, GLint64 *params); + + // Core in OpenGL >=3.3 and provided by ARB_timer_query + void (QOPENGLF_APIENTRYP GetQueryObjectui64v)(GLuint id, GLenum pname, GLuint64 *params); + void (QOPENGLF_APIENTRYP GetQueryObjecti64v)(GLuint id, GLenum pname, GLint64 *params); + void (QOPENGLF_APIENTRYP QueryCounter)(GLuint id, GLenum target); +}; + +QT_END_NAMESPACE + +#endif + +#endif // QOPENGLQUERYHELPER_P_H diff --git a/src/opengl/qopengltimerquery.cpp b/src/opengl/qopengltimerquery.cpp new file mode 100644 index 0000000000..44955d48fc --- /dev/null +++ b/src/opengl/qopengltimerquery.cpp @@ -0,0 +1,880 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtOpenGL module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qopengltimerquery.h" + +#include "qopenglqueryhelper_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +// Helper class used as fallback if OpenGL <3.3 is being used with EXT_timer_query +class QExtTimerQueryHelper +{ +public: + QExtTimerQueryHelper(QOpenGLContext *context) + { + Q_ASSERT(context); + GetQueryObjectui64vEXT = reinterpret_cast(context->getProcAddress("glGetQueryObjectui64vEXT")); + GetQueryObjecti64vEXT = reinterpret_cast(context->getProcAddress("glGetQueryObjecti64vEXT")); + } + + inline void glGetQueryObjectui64vEXT(GLuint id, GLenum pname, GLuint64EXT *params) + { + GetQueryObjectui64vEXT(id, pname, params); + } + + inline void glGetQueryObjecti64vEXT(GLuint id, GLenum pname, GLint64EXT *params) + { + GetQueryObjecti64vEXT(id, pname, params); + } + +private: + void (QOPENGLF_APIENTRYP GetQueryObjectui64vEXT)(GLuint id, GLenum pname, GLuint64EXT *params); + void (QOPENGLF_APIENTRYP GetQueryObjecti64vEXT)(GLuint id, GLenum pname, GLint64EXT *params); +}; + +class QOpenGLTimerQueryPrivate : public QObjectPrivate +{ +public: + QOpenGLTimerQueryPrivate() + : QObjectPrivate(), + context(nullptr), + ext(nullptr), + timeInterval(0), + timer(0) + { + } + + ~QOpenGLTimerQueryPrivate() + { + delete core; + delete ext; + } + + bool create(); + void destroy(); + void begin(); + void end(); + GLuint64 waitForTimeStamp() const; + void recordTimestamp(); + bool isResultAvailable() const; + GLuint64 result() const; + + // There are several cases we must handle: + // OpenGL >=3.3 includes timer queries as a core feature + // ARB_timer_query has same functionality as above. Requires OpenGL 3.2 + // EXT_timer_query offers limited support. Can be used with OpenGL >=1.5 + // + // Note that some implementations (OS X) provide OpenGL 3.2 but do not expose the + // ARB_timer_query extension. In such situations we must also be able to handle + // using the EXT_timer_query extension with any version of OpenGL. + // + // OpenGL 1.5 or above contains the generic query API and OpenGL 3.3 and + // ARB_timer_query provide the 64-bit query API. These are wrapped by + // QOpenGLQueryHelper. All we need to handle in addition is the EXT_timer_query + // case and to take care not to call the Core/ARB functions when we only + // have EXT_timer_query available. + QOpenGLContext *context; + QOpenGLQueryHelper *core; + QExtTimerQueryHelper *ext; + mutable GLuint64 timeInterval; + GLuint timer; +}; + +bool QOpenGLTimerQueryPrivate::create() +{ + QOpenGLContext *ctx = QOpenGLContext::currentContext(); + + if (timer && context == ctx) + return true; + + context = ctx; + if (!context) { + qWarning("A current OpenGL context is required to create timer query objects"); + return false; + } + + if (context->isOpenGLES()) { + qWarning("QOpenGLTimerQuery: Not supported on OpenGL ES"); + return false; + } + + // Resolve the functions provided by OpenGL 1.5 and OpenGL 3.3 or ARB_timer_query + core = new QOpenGLQueryHelper(context); + + // Check to see if we also need to resolve the functions for EXT_timer_query + QSurfaceFormat f = context->format(); + if (f.version() <= qMakePair(3, 2) + && !context->hasExtension(QByteArrayLiteral("GL_ARB_timer_query")) + && context->hasExtension(QByteArrayLiteral("GL_EXT_timer_query"))) { + ext = new QExtTimerQueryHelper(context); + } else if (f.version() <= qMakePair(3, 2) + && !context->hasExtension(QByteArrayLiteral("GL_ARB_timer_query")) + && !context->hasExtension(QByteArrayLiteral("GL_EXT_timer_query"))) { + qWarning("QOpenGLTimerQuery requires one of:\n" + " OpenGL 3.3 or newer,\n" + " OpenGL 3.2 and the ARB_timer_query extension\n" + " or the EXT_timer query extension"); + return false; + } + + core->glGenQueries(1, &timer); + return (timer != 0); +} + +void QOpenGLTimerQueryPrivate::destroy() +{ + if (!timer) + return; + + core->glDeleteQueries(1, &timer); + timer = 0; + context = nullptr; +} + +// GL_TIME_ELAPSED_EXT is not defined on OS X 10.6 +#if !defined(GL_TIME_ELAPSED_EXT) +#define GL_TIME_ELAPSED_EXT 0x88BF +#endif + +// GL_TIME_ELAPSED is not defined on OS X 10.7 or 10.8 yet +#if !defined(GL_TIME_ELAPSED) +#define GL_TIME_ELAPSED GL_TIME_ELAPSED_EXT +#endif + +void QOpenGLTimerQueryPrivate::begin() +{ + core->glBeginQuery(GL_TIME_ELAPSED, timer); +} + +void QOpenGLTimerQueryPrivate::end() +{ + core->glEndQuery(GL_TIME_ELAPSED); +} + +void QOpenGLTimerQueryPrivate::recordTimestamp() +{ + // Don't call glQueryCounter if we only have EXT_timer_query +#if defined(GL_TIMESTAMP) + if (!ext) + core->glQueryCounter(timer, GL_TIMESTAMP); + else + qWarning("QOpenGLTimerQuery::recordTimestamp() requires OpenGL 3.3 or GL_ARB_timer_query"); +#else + qWarning("QOpenGLTimerQuery::recordTimestamp() requires OpenGL 3.3 or GL_ARB_timer_query"); +#endif +} + +GLuint64 QOpenGLTimerQueryPrivate::waitForTimeStamp() const +{ + GLint64 tmp = 0; +#if defined(GL_TIMESTAMP) + if (!ext) + core->glGetInteger64v(GL_TIMESTAMP, &tmp); + else + qWarning("QOpenGLTimerQuery::waitForTimestamp() requires OpenGL 3.3 or GL_ARB_timer_query"); +#else + qWarning("QOpenGLTimerQuery::waitForTimestamp() requires OpenGL 3.3 or GL_ARB_timer_query"); +#endif + GLuint64 timestamp(tmp); + return timestamp; +} + +bool QOpenGLTimerQueryPrivate::isResultAvailable() const +{ + GLuint available = GL_FALSE; + core->glGetQueryObjectuiv(timer, GL_QUERY_RESULT_AVAILABLE, &available); + return available; +} + +GLuint64 QOpenGLTimerQueryPrivate::result() const +{ + if (!ext) + core->glGetQueryObjectui64v(timer, GL_QUERY_RESULT, &timeInterval); + else + ext->glGetQueryObjectui64vEXT(timer, GL_QUERY_RESULT, &timeInterval); + return timeInterval; +} + +/*! + \class QOpenGLTimerQuery + \brief The QOpenGLTimerQuery class wraps an OpenGL timer query object. + \inmodule QtOpenGL + \since 5.1 + \ingroup painting-3D + + OpenGL timer query objects are OpenGL managed resources to measure the + execution times of sequences of OpenGL commands on the GPU. + + OpenGL offers various levels of support for timer queries, depending on + the version of OpenGL you have and the presence of the ARB_timer_query or + EXT_timer_query extensions. The support can be summarized as: + + \list + \li OpenGL >=3.3 offers full support for all timer query functionality. + \li OpenGL 3.2 with the ARB_timer_query extension offers full support + for all timer query functionality. + \li OpenGL <=3.2 with the EXT_timer_query extension offers limited support + in that the timestamp of the GPU cannot be queried. Places where this + impacts functions provided by Qt classes will be highlighted in the + function documentation. + \li OpenGL ES 2 (and OpenGL ES 3) do not provide any support for OpenGL + timer queries. + \endlist + + OpenGL represents time with a granularity of 1 nanosecond (1e-9 seconds). As a + consequence of this, 32-bit integers would only give a total possible duration + of approximately 4 seconds, which would not be difficult to exceed in poorly + performing or lengthy operations. OpenGL therefore uses 64 bit integer types + to represent times. A GLuint64 variable has enough width to contain a duration + of hundreds of years, which is plenty for real-time rendering needs. + + As with the other Qt OpenGL classes, QOpenGLTimerQuery has a create() + function to create the underlying OpenGL object. This is to allow the developer to + ensure that there is a valid current OpenGL context at the time. + + Once created, timer queries can be issued in one of several ways. The simplest + method is to delimit a block of commands with calls to begin() and end(). This + instructs OpenGL to measure the time taken from completing all commands issued + prior to begin() until the completion of all commands issued prior to end(). + + At the end of a frame we can retrieve the results by calling waitForResult(). + As this function's name implies, it blocks CPU execution until OpenGL notifies + that the timer query result is available. To avoid blocking, you can check + if the query result is available by calling isResultAvailable(). Note that + modern GPUs are deeply pipelined and query results may not become available for + between 1-5 frames after they were issued. + + Note that OpenGL does not permit nesting or interleaving of multiple timer queries + using begin() and end(). Using multiple timer queries and recordTimestamp() avoids + this limitation. When using recordTimestamp() the result can be obtained at + some later time using isResultAvailable() and waitForResult(). Qt provides the + convenience class QOpenGLTimeMonitor that helps with using multiple query objects. + + \sa QOpenGLTimeMonitor +*/ + +/*! + Creates a QOpenGLTimerQuery instance with the given \a parent. You must call create() + with a valid OpenGL context before using. +*/ +QOpenGLTimerQuery::QOpenGLTimerQuery(QObject *parent) + : QObject(*new QOpenGLTimerQueryPrivate, parent) +{ +} + +/*! + Destroys the QOpenGLTimerQuery and the underlying OpenGL resource. +*/ +QOpenGLTimerQuery::~QOpenGLTimerQuery() +{ + QOpenGLContext* ctx = QOpenGLContext::currentContext(); + + Q_D(QOpenGLTimerQuery); + QOpenGLContext *oldContext = nullptr; + if (d->context != ctx) { + oldContext = ctx; + if (d->context->makeCurrent(oldContext->surface())) { + ctx = d->context; + } else { + qWarning("QOpenGLTimerQuery::~QOpenGLTimerQuery() failed to make query objects's context current"); + ctx = nullptr; + } + } + + if (ctx) + destroy(); + + if (oldContext) { + if (!oldContext->makeCurrent(oldContext->surface())) + qWarning("QOpenGLTimerQuery::~QOpenGLTimerQuery() failed to restore current context"); + } +} + +/*! + Creates the underlying OpenGL timer query object. There must be a valid OpenGL context + that supports query objects current for this function to succeed. + + Returns \c true if the OpenGL timer query object was successfully created. +*/ +bool QOpenGLTimerQuery::create() +{ + Q_D(QOpenGLTimerQuery); + return d->create(); +} + +/*! + Destroys the underlying OpenGL timer query object. The context that was current when + create() was called must be current when calling this function. +*/ +void QOpenGLTimerQuery::destroy() +{ + Q_D(QOpenGLTimerQuery); + d->destroy(); +} + +/*! + Returns \c true if the underlying OpenGL query object has been created. If this + returns \c true and the associated OpenGL context is current, then you are able to issue + queries with this object. +*/ +bool QOpenGLTimerQuery::isCreated() const +{ + Q_D(const QOpenGLTimerQuery); + return (d->timer != 0); +} + +/*! + Returns the id of the underlying OpenGL query object. +*/ +GLuint QOpenGLTimerQuery::objectId() const +{ + Q_D(const QOpenGLTimerQuery); + return d->timer; +} + +/*! + Marks the start point in the OpenGL command queue for a sequence of commands to + be timed by this query object. + + This is useful for simple use-cases. Usually it is better to use recordTimestamp(). + + \sa end(), isResultAvailable(), waitForResult(), recordTimestamp() +*/ +void QOpenGLTimerQuery::begin() +{ + Q_D(QOpenGLTimerQuery); + d->begin(); +} + +/*! + Marks the end point in the OpenGL command queue for a sequence of commands to + be timed by this query object. + + This is useful for simple use-cases. Usually it is better to use recordTimestamp(). + + \sa begin(), isResultAvailable(), waitForResult(), recordTimestamp() +*/ +void QOpenGLTimerQuery::end() +{ + Q_D(QOpenGLTimerQuery); + d->end(); +} + +/*! + Places a marker in the OpenGL command queue for the GPU to record the timestamp + when this marker is reached by the GPU. This function is non-blocking and the + result will become available at some later time. + + The availability of the result can be checked with isResultAvailable(). The result + can be fetched with waitForResult() which will block if the result is not yet + available. + + \sa waitForResult(), isResultAvailable(), begin(), end() +*/ +void QOpenGLTimerQuery::recordTimestamp() +{ + Q_D(QOpenGLTimerQuery); + return d->recordTimestamp(); +} + +/*! + Returns the current timestamp of the GPU when all previously issued OpenGL + commands have been received but not necessarily executed by the GPU. + + This function blocks until the result is returned. + + \sa recordTimestamp() +*/ +GLuint64 QOpenGLTimerQuery::waitForTimestamp() const +{ + Q_D(const QOpenGLTimerQuery); + return d->waitForTimeStamp(); +} + +/*! + Returns \c true if the OpenGL timer query result is available. + + This function is non-blocking and ideally should be used to check for the + availability of the query result before calling waitForResult(). + + \sa waitForResult() +*/ +bool QOpenGLTimerQuery::isResultAvailable() const +{ + Q_D(const QOpenGLTimerQuery); + return d->isResultAvailable(); +} + +/*! + Returns the result of the OpenGL timer query. + + This function will block until the result is made available by OpenGL. It is + recommended to call isResultAvailable() to ensure that the result is available + to avoid unnecessary blocking and stalling. + + \sa isResultAvailable() +*/ +GLuint64 QOpenGLTimerQuery::waitForResult() const +{ + Q_D(const QOpenGLTimerQuery); + return d->result(); +} + + +class QOpenGLTimeMonitorPrivate : public QObjectPrivate +{ +public: + QOpenGLTimeMonitorPrivate() + : QObjectPrivate(), + timers(), + timeSamples(), + context(nullptr), + core(nullptr), + ext(nullptr), + requestedSampleCount(2), + currentSample(-1), + timerQueryActive(false) + { + } + + ~QOpenGLTimeMonitorPrivate() + { + delete core; + delete ext; + } + + bool create(); + void destroy(); + void recordSample(); + bool isResultAvailable() const; + QVector samples() const; + QVector intervals() const; + void reset(); + + QVector timers; + mutable QVector timeSamples; + + QOpenGLContext *context; + QOpenGLQueryHelper *core; + QExtTimerQueryHelper *ext; + + int requestedSampleCount; + int currentSample; + mutable bool timerQueryActive; +}; + +bool QOpenGLTimeMonitorPrivate::create() +{ + if (!timers.isEmpty() && timers.at(0) != 0 && timers.size() == requestedSampleCount) + return true; + + QOpenGLContext *ctx = QOpenGLContext::currentContext(); + if (context && context != ctx) { + qWarning("QTimeMonitor: Attempting to use different OpenGL context to recreate timers.\n" + "Please call destroy() first or use the same context to previously create"); + return false; + } + + context = ctx; + if (!context) { + qWarning("A current OpenGL context is required to create timer query objects"); + return false; + } + + // Resize the vectors that hold the timers and the recorded samples + timers.resize(requestedSampleCount); + timeSamples.resize(requestedSampleCount); + + // Resolve the functions provided by OpenGL 1.5 and OpenGL 3.3 or ARB_timer_query + core = new QOpenGLQueryHelper(context); + + // Check to see if we also need to resolve the functions for EXT_timer_query + QSurfaceFormat f = context->format(); + if (f.version() <= qMakePair(3, 2) + && !context->hasExtension(QByteArrayLiteral("GL_ARB_timer_query")) + && context->hasExtension(QByteArrayLiteral("GL_EXT_timer_query"))) { + ext = new QExtTimerQueryHelper(context); + } else if (f.version() <= qMakePair(3, 2) + && !context->hasExtension(QByteArrayLiteral("GL_ARB_timer_query")) + && !context->hasExtension(QByteArrayLiteral("GL_EXT_timer_query"))) { + qWarning("QOpenGLTimeMonitor requires one of:\n" + " OpenGL 3.3 or newer,\n" + " OpenGL 3.2 and the ARB_timer_query extension\n" + " or the EXT_timer query extension"); + return false; + } + + core->glGenQueries(requestedSampleCount, timers.data()); + return (timers.at(0) != 0); +} + +void QOpenGLTimeMonitorPrivate::destroy() +{ + if (timers.isEmpty() || timers.at(0) == 0) + return; + + core->glDeleteQueries(timers.size(), timers.data()); + timers.clear(); + delete core; + core = nullptr; + delete ext; + ext = nullptr; + context = nullptr; +} + +void QOpenGLTimeMonitorPrivate::recordSample() +{ + // Use glQueryCounter() and GL_TIMESTAMP where available. + // Otherwise, simulate it with glBeginQuery()/glEndQuery() + if (!ext) { +#if defined(GL_TIMESTAMP) + core->glQueryCounter(timers.at(++currentSample), GL_TIMESTAMP); +#endif + } else { + if (currentSample == -1) { + core->glBeginQuery(GL_TIME_ELAPSED_EXT, timers.at(++currentSample)); + timerQueryActive = true; + } else if (currentSample < timers.size() - 1) { + core->glEndQuery(GL_TIME_ELAPSED_EXT); + core->glBeginQuery(GL_TIME_ELAPSED_EXT, timers.at(++currentSample)); + } else { + if (timerQueryActive) { + core->glEndQuery(GL_TIME_ELAPSED_EXT); + timerQueryActive = false; + } + } + } +} + +bool QOpenGLTimeMonitorPrivate::isResultAvailable() const +{ + // The OpenGL spec says that if a query result is ready then the results of all queries + // of the same type issued before it must also be ready. Therefore we only need to check + // the availability of the result for the last issued query + GLuint available = GL_FALSE; + core->glGetQueryObjectuiv(timers.at(currentSample), GL_QUERY_RESULT_AVAILABLE, &available); + return available; +} + +QVector QOpenGLTimeMonitorPrivate::samples() const +{ + // For the Core and ARB options just ask for the timestamp for each timer query. + // For the EXT implementation we cannot obtain timestamps so we defer any result + // collection to the intervals() function + if (!ext) { + for (int i = 0; i <= currentSample; ++i) + core->glGetQueryObjectui64v(timers.at(i), GL_QUERY_RESULT, &timeSamples[i]); + } else { + qWarning("QOpenGLTimeMonitor::samples() requires OpenGL >=3.3\n" + "or OpenGL 3.2 and GL_ARB_timer_query"); + } + return timeSamples; +} + +QVector QOpenGLTimeMonitorPrivate::intervals() const +{ + QVector intervals(timers.size() - 1); + if (!ext) { + // Obtain the timestamp samples and calculate the interval durations + const QVector timeStamps = samples(); + for (int i = 0; i < intervals.size(); ++i) + intervals[i] = timeStamps[i+1] - timeStamps[i]; + } else { + // Stop the last timer if needed + if (timerQueryActive) { + core->glEndQuery(GL_TIME_ELAPSED_EXT); + timerQueryActive = false; + } + + // Obtain the results from all timers apart from the redundant last one. In this + // case the results actually are the intervals not timestamps + for (int i = 0; i < currentSample; ++i) + ext->glGetQueryObjectui64vEXT(timers.at(i), GL_QUERY_RESULT, &intervals[i]); + } + + return intervals; +} + +void QOpenGLTimeMonitorPrivate::reset() +{ + currentSample = -1; + timeSamples.fill(0); +} + + +/*! + \class QOpenGLTimeMonitor + \brief The QOpenGLTimeMonitor class wraps a sequence of OpenGL timer query objects. + \inmodule QtOpenGL + \since 5.1 + \ingroup painting-3D + + The QOpenGLTimeMonitor class is a convenience wrapper around a collection of OpenGL + timer query objects used to measure intervals of time on the GPU to the level of + granularity required by your rendering application. + + The OpenGL timer queries objects are queried in sequence to record the GPU + timestamps at positions of interest in your rendering code. Once the results for + all issues timer queries become available, the results can be fetched and + QOpenGLTimerMonitor will calculate the recorded time intervals for you. + + The typical use case of this class is to either profile your application's rendering + algorithms or to adjust those algorithms in real-time for dynamic performance/quality + balancing. + + Prior to using QOpenGLTimeMonitor in your rendering function you should set the + required number of sample points that you wish to record by calling setSamples(). Note + that measuring N sample points will produce N-1 time intervals. Once you have set the + number of sample points, call the create() function with a valid current OpenGL context + to create the necessary query timer objects. These steps are usually performed just + once in an initialization function. + + Use the recordSample() function to delimit blocks of code containing OpenGL commands + that you wish to time. You can check availability of the resulting time + samples and time intervals with isResultAvailable(). The calculated time intervals and + the raw timestamp samples can be retrieved with the blocking waitForIntervals() and + waitForSamples() functions respectively. + + After retrieving the results and before starting a new round of taking samples + (for example, in the next frame) be sure to call the reset() function which will clear + the cached results and reset the timer index back to the first timer object. + + \sa QOpenGLTimerQuery +*/ + +/*! + Creates a QOpenGLTimeMonitor instance with the given \a parent. You must call create() + with a valid OpenGL context before using. + + \sa setSampleCount(), create() +*/ +QOpenGLTimeMonitor::QOpenGLTimeMonitor(QObject *parent) + : QObject(*new QOpenGLTimeMonitorPrivate, parent) +{ +} + +/*! + Destroys the QOpenGLTimeMonitor and any underlying OpenGL resources. +*/ +QOpenGLTimeMonitor::~QOpenGLTimeMonitor() +{ + QOpenGLContext* ctx = QOpenGLContext::currentContext(); + + Q_D(QOpenGLTimeMonitor); + QOpenGLContext *oldContext = nullptr; + if (d->context != ctx) { + oldContext = ctx; + if (d->context->makeCurrent(oldContext->surface())) { + ctx = d->context; + } else { + qWarning("QOpenGLTimeMonitor::~QOpenGLTimeMonitor() failed to make time monitor's context current"); + ctx = nullptr; + } + } + + if (ctx) + destroy(); + + if (oldContext) { + if (!oldContext->makeCurrent(oldContext->surface())) + qWarning("QOpenGLTimeMonitor::~QOpenGLTimeMonitor() failed to restore current context"); + } +} + +/*! + Sets the number of sample points to \a sampleCount. After setting the number + of samples with this function, you must call create() to instantiate the underlying + OpenGL timer query objects. + + The new \a sampleCount must be at least 2. + + \sa sampleCount(), create(), recordSample() +*/ +void QOpenGLTimeMonitor::setSampleCount(int sampleCount) +{ + // We need at least 2 samples to get an interval + if (sampleCount < 2) + return; + Q_D(QOpenGLTimeMonitor); + d->requestedSampleCount = sampleCount; +} + +/*! + Returns the number of sample points that have been requested with + setSampleCount(). If create was successfully called following setSampleCount(), + then the value returned will be the actual number of sample points + that can be used. + + The default value for sample count is 2, leading to the measurement of a + single interval. + + \sa setSampleCount() +*/ +int QOpenGLTimeMonitor::sampleCount() const +{ + Q_D(const QOpenGLTimeMonitor); + return d->requestedSampleCount; +} + +/*! + Instantiate sampleCount() OpenGL timer query objects that will be used + to track the amount of time taken to execute OpenGL commands between + successive calls to recordSample(). + + Returns \c true if the OpenGL timer query objects could be created. + + \sa destroy(), setSampleCount(), recordSample() +*/ +bool QOpenGLTimeMonitor::create() +{ + Q_D(QOpenGLTimeMonitor); + return d->create(); +} + +/*! + Destroys any OpenGL timer query objects used within this instance. + + \sa create() +*/ +void QOpenGLTimeMonitor::destroy() +{ + Q_D(QOpenGLTimeMonitor); + d->destroy(); +} + +/*! + Returns \c true if the underlying OpenGL query objects have been created. If this + returns \c true and the associated OpenGL context is current, then you are able to record + time samples with this object. +*/ +bool QOpenGLTimeMonitor::isCreated() const +{ + Q_D(const QOpenGLTimeMonitor); + return (!d->timers.isEmpty() && d->timers.at(0) != 0); +} + +/*! + Returns a QVector containing the object Ids of the OpenGL timer query objects. +*/ +QVector QOpenGLTimeMonitor::objectIds() const +{ + Q_D(const QOpenGLTimeMonitor); + return d->timers; +} + +/*! + Issues an OpenGL timer query at this point in the OpenGL command queue. Calling this + function in a sequence in your application's rendering function, will build up + details of the GPU time taken to execute the OpenGL commands between successive + calls to this function. + + \sa setSampleCount(), isResultAvailable(), waitForSamples(), waitForIntervals() +*/ +int QOpenGLTimeMonitor::recordSample() +{ + Q_D(QOpenGLTimeMonitor); + d->recordSample(); + return d->currentSample; +} + +/*! + Returns \c true if the OpenGL timer query results are available. + + \sa waitForSamples(), waitForIntervals() +*/ +bool QOpenGLTimeMonitor::isResultAvailable() const +{ + Q_D(const QOpenGLTimeMonitor); + return d->isResultAvailable(); +} + +/*! + Returns a QVector containing the GPU timestamps taken with recordSample(). + + This function will block until OpenGL indicates the results are available. It + is recommended to check the availability of the result prior to calling this + function with isResultAvailable(). + + \note This function only works on systems that have OpenGL >=3.3 or the + ARB_timer_query extension. See QOpenGLTimerQuery for more details. + + \sa waitForIntervals(), isResultAvailable() +*/ +QVector QOpenGLTimeMonitor::waitForSamples() const +{ + Q_D(const QOpenGLTimeMonitor); + return d->samples(); +} + +/*! + Returns a QVector containing the time intervals delimited by the calls to + recordSample(). The resulting vector will contain one fewer element as + this represents the intervening intervals rather than the actual timestamp + samples. + + This function will block until OpenGL indicates the results are available. It + is recommended to check the availability of the result prior to calling this + function with isResultAvailable(). + + \sa waitForSamples(), isResultAvailable() +*/ +QVector QOpenGLTimeMonitor::waitForIntervals() const +{ + Q_D(const QOpenGLTimeMonitor); + return d->intervals(); +} + +/*! + Resets the time monitor ready for use in another frame of rendering. Call + this once you have obtained the previous results and before calling + recordSample() for the first time on the next frame. + + \sa recordSample() +*/ +void QOpenGLTimeMonitor::reset() +{ + Q_D(QOpenGLTimeMonitor); + d->reset(); +} + +QT_END_NAMESPACE diff --git a/src/opengl/qopengltimerquery.h b/src/opengl/qopengltimerquery.h new file mode 100644 index 0000000000..a779240108 --- /dev/null +++ b/src/opengl/qopengltimerquery.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtOpenGL module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QOPENGLTIMERQUERY_H +#define QOPENGLTIMERQUERY_H + +#include + +#if !defined(QT_NO_OPENGL) && !defined(QT_OPENGL_ES_2) + +#include +#include + +QT_BEGIN_NAMESPACE + +class QOpenGLTimerQueryPrivate; + +class Q_OPENGL_EXPORT QOpenGLTimerQuery : public QObject +{ + Q_OBJECT + +public: + explicit QOpenGLTimerQuery(QObject *parent = nullptr); + ~QOpenGLTimerQuery(); + + bool create(); + void destroy(); + bool isCreated() const; + GLuint objectId() const; + + void begin(); + void end(); + GLuint64 waitForTimestamp() const; + void recordTimestamp(); + bool isResultAvailable() const; + GLuint64 waitForResult() const; + +private: + Q_DECLARE_PRIVATE(QOpenGLTimerQuery) + Q_DISABLE_COPY(QOpenGLTimerQuery) +}; + + +class QOpenGLTimeMonitorPrivate; + +class Q_OPENGL_EXPORT QOpenGLTimeMonitor : public QObject +{ + Q_OBJECT + +public: + explicit QOpenGLTimeMonitor(QObject *parent = nullptr); + ~QOpenGLTimeMonitor(); + + void setSampleCount(int sampleCount); + int sampleCount() const; + + bool create(); + void destroy(); + bool isCreated() const; + QVector objectIds() const; + + int recordSample(); + + bool isResultAvailable() const; + + QVector waitForSamples() const; + QVector waitForIntervals() const; + + void reset(); + +private: + Q_DECLARE_PRIVATE(QOpenGLTimeMonitor) + Q_DISABLE_COPY(QOpenGLTimeMonitor) +}; + +QT_END_NAMESPACE + +#endif // QT_NO_OPENGL + +#endif // QOPENGLTIMERQUERY_H -- cgit v1.2.3