rhi: gl: Implement lastCompletedGpuTime()

Only for OpenGL 3.3+ (not ES).

Change-Id: I016ba934d7f5038b3d56f8d0c62e72b3921d86c6
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
This commit is contained in:
Laszlo Agocs 2023-08-10 18:38:21 +02:00
parent 72a453c6a8
commit 4580003341
3 changed files with 159 additions and 18 deletions

View File

@ -702,9 +702,12 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
QRhiCommandBuffer::debugMarkBegin()) are supported.
\value Timestamps Indicates that command buffer timestamps are supported.
Relevant for QRhiCommandBuffer::lastCompletedGpuTime(). Can be expected to
be supported on Metal, Vulkan, and Direct 3D, assuming the underlying
implementation supports timestamp queries or similar.
Relevant for QRhiCommandBuffer::lastCompletedGpuTime(). This can be
expected to be supported on Metal, Vulkan, Direct 3D 11 and 12, and OpenGL
contexts of version 3.3 or newer. However, with some of these APIs support
for timestamp queries is technically optional, and therefore it cannot be
guaranteed that this feature is always supported with every implementation
of them.
\value Instancing Indicates that instanced drawing is supported. In
practice this feature will be unsupported with OpenGL ES 2.0 and OpenGL

View File

@ -492,21 +492,33 @@ QT_BEGIN_NAMESPACE
#endif
#ifndef GL_TEXTURE_1D
# define GL_TEXTURE_1D 0x0DE0
# define GL_TEXTURE_1D 0x0DE0
#endif
#ifndef GL_TEXTURE_1D_ARRAY
# define GL_TEXTURE_1D_ARRAY 0x8C18
# define GL_TEXTURE_1D_ARRAY 0x8C18
#endif
#ifndef GL_HALF_FLOAT
#define GL_HALF_FLOAT 0x140B
#define GL_HALF_FLOAT 0x140B
#endif
#ifndef GL_MAX_VERTEX_OUTPUT_COMPONENTS
#define GL_MAX_VERTEX_OUTPUT_COMPONENTS 0x9122
#endif
#ifndef GL_TIMESTAMP
#define GL_TIMESTAMP 0x8E28
#endif
#ifndef GL_QUERY_RESULT
#define GL_QUERY_RESULT 0x8866
#endif
#ifndef GL_QUERY_RESULT_AVAILABLE
#define GL_QUERY_RESULT_AVAILABLE 0x8867
#endif
/*!
Constructs a new QRhiGles2InitParams.
@ -1026,6 +1038,17 @@ bool QRhiGles2::create(QRhi::Flags flags)
ctx->getProcAddress(QByteArrayLiteral("glFramebufferTextureMultiviewOVR")));
}
// Only do timestamp queries on OpenGL 3.3+.
caps.timestamps = !caps.gles && (caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 3));
if (caps.timestamps) {
glQueryCounter = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLuint, GLenum)>(
ctx->getProcAddress(QByteArrayLiteral("glQueryCounter")));
glGetQueryObjectui64v = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLuint, GLenum, quint64 *)>(
ctx->getProcAddress(QByteArrayLiteral("glGetQueryObjectui64v")));
if (!glQueryCounter || !glGetQueryObjectui64v)
caps.timestamps = false;
}
nativeHandlesStruct.context = ctx;
contextLost = false;
@ -1041,6 +1064,11 @@ void QRhiGles2::destroy()
ensureContext();
executeDeferredReleases();
if (ofr.tsQueries[0]) {
f->glDeleteQueries(2, ofr.tsQueries);
ofr.tsQueries[0] = ofr.tsQueries[1] = 0;
}
if (vao) {
f->glDeleteVertexArrays(1, &vao);
vao = 0;
@ -1315,7 +1343,7 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const
case QRhi::DebugMarkers:
return false;
case QRhi::Timestamps:
return false;
return caps.timestamps;
case QRhi::Instancing:
return caps.instancing;
case QRhi::CustomInstanceStepRate:
@ -1964,10 +1992,14 @@ const QRhiNativeHandles *QRhiGles2::nativeHandles(QRhiCommandBuffer *cb)
return nullptr;
}
static inline void addBoundaryCommand(QGles2CommandBuffer *cbD, QGles2CommandBuffer::Command::Cmd type)
static inline void addBoundaryCommand(QGles2CommandBuffer *cbD, QGles2CommandBuffer::Command::Cmd type, GLuint tsQuery = 0)
{
QGles2CommandBuffer::Command &cmd(cbD->commands.get());
cmd.cmd = type;
if (type == QGles2CommandBuffer::Command::BeginFrame)
cmd.args.beginFrame.timestampQuery = tsQuery;
else if (type == QGles2CommandBuffer::Command::EndFrame)
cmd.args.endFrame.timestampQuery = tsQuery;
}
void QRhiGles2::beginExternal(QRhiCommandBuffer *cb)
@ -2029,8 +2061,8 @@ void QRhiGles2::endExternal(QRhiCommandBuffer *cb)
double QRhiGles2::lastCompletedGpuTime(QRhiCommandBuffer *cb)
{
Q_UNUSED(cb);
return 0;
QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
return cbD->lastGpuTime;
}
QRhi::FrameOpResult QRhiGles2::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags)
@ -2046,7 +2078,17 @@ QRhi::FrameOpResult QRhiGles2::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF
executeDeferredReleases();
swapChainD->cb.resetState();
addBoundaryCommand(&swapChainD->cb, QGles2CommandBuffer::Command::BeginFrame);
if (swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex]) {
double elapsedSec = 0;
if (swapChainD->timestamps.tryQueryTimestamps(swapChainD->currentTimestampPairIndex, this, &elapsedSec))
swapChainD->cb.lastGpuTime = elapsedSec;
}
GLuint tsStart = swapChainD->timestamps.query[swapChainD->currentTimestampPairIndex * 2];
GLuint tsEnd = swapChainD->timestamps.query[swapChainD->currentTimestampPairIndex * 2 + 1];
const bool recordTimestamps = tsStart && tsEnd && !swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex];
addBoundaryCommand(&swapChainD->cb, QGles2CommandBuffer::Command::BeginFrame, recordTimestamps ? tsStart : 0);
return QRhi::FrameOpSuccess;
}
@ -2056,7 +2098,15 @@ QRhi::FrameOpResult QRhiGles2::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame
QGles2SwapChain *swapChainD = QRHI_RES(QGles2SwapChain, swapChain);
Q_ASSERT(currentSwapChain == swapChainD);
addBoundaryCommand(&swapChainD->cb, QGles2CommandBuffer::Command::EndFrame);
GLuint tsStart = swapChainD->timestamps.query[swapChainD->currentTimestampPairIndex * 2];
GLuint tsEnd = swapChainD->timestamps.query[swapChainD->currentTimestampPairIndex * 2 + 1];
const bool recordTimestamps = tsStart && tsEnd && !swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex];
if (recordTimestamps) {
swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex] = true;
swapChainD->currentTimestampPairIndex = (swapChainD->currentTimestampPairIndex + 1) % QGles2SwapChainTimestamps::TIMESTAMP_PAIRS;
}
addBoundaryCommand(&swapChainD->cb, QGles2CommandBuffer::Command::EndFrame, recordTimestamps ? tsEnd : 0);
if (!ensureContext(swapChainD->surface))
return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError;
@ -2088,7 +2138,12 @@ QRhi::FrameOpResult QRhiGles2::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi:
executeDeferredReleases();
ofr.cbWrapper.resetState();
addBoundaryCommand(&ofr.cbWrapper, QGles2CommandBuffer::Command::BeginFrame);
if (rhiFlags.testFlag(QRhi::EnableTimestamps) && caps.timestamps) {
if (!ofr.tsQueries[0])
f->glGenQueries(2, ofr.tsQueries);
}
addBoundaryCommand(&ofr.cbWrapper, QGles2CommandBuffer::Command::BeginFrame, ofr.tsQueries[0]);
*cb = &ofr.cbWrapper;
return QRhi::FrameOpSuccess;
@ -2100,7 +2155,7 @@ QRhi::FrameOpResult QRhiGles2::endOffscreenFrame(QRhi::EndFrameFlags flags)
Q_ASSERT(ofr.active);
ofr.active = false;
addBoundaryCommand(&ofr.cbWrapper, QGles2CommandBuffer::Command::EndFrame);
addBoundaryCommand(&ofr.cbWrapper, QGles2CommandBuffer::Command::EndFrame, ofr.tsQueries[1]);
if (!ensureContext())
return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError;
@ -2113,6 +2168,16 @@ QRhi::FrameOpResult QRhiGles2::endOffscreenFrame(QRhi::EndFrameFlags flags)
// another, sharing context.
f->glFlush();
if (ofr.tsQueries[0]) {
quint64 timestamps[2];
glGetQueryObjectui64v(ofr.tsQueries[1], GL_QUERY_RESULT, &timestamps[1]);
glGetQueryObjectui64v(ofr.tsQueries[0], GL_QUERY_RESULT, &timestamps[0]);
if (timestamps[1] >= timestamps[0]) {
const quint64 nanoseconds = timestamps[1] - timestamps[0];
ofr.cbWrapper.lastGpuTime = nanoseconds / 1000000000.0; // seconds
}
}
return QRhi::FrameOpSuccess;
}
@ -2867,6 +2932,8 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
const QGles2CommandBuffer::Command &cmd(*it);
switch (cmd.cmd) {
case QGles2CommandBuffer::Command::BeginFrame:
if (cmd.args.beginFrame.timestampQuery)
glQueryCounter(cmd.args.beginFrame.timestampQuery, GL_TIMESTAMP);
if (caps.coreProfile) {
if (!vao)
f->glGenVertexArrays(1, &vao);
@ -2893,6 +2960,8 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
#endif
if (vao)
f->glBindVertexArray(0);
if (cmd.args.endFrame.timestampQuery)
glQueryCounter(cmd.args.endFrame.timestampQuery, GL_TIMESTAMP);
break;
case QGles2CommandBuffer::Command::ResetFrame:
if (vao)
@ -6210,15 +6279,56 @@ bool QGles2SwapChain::createOrResize()
frameCount = 0;
QRHI_RES_RHI(QRhiGles2);
if (rhiD->rhiFlags.testFlag(QRhi::EnableTimestamps) && rhiD->caps.timestamps)
timestamps.prepare(rhiD);
// The only reason to register this fairly fake gl swapchain
// object with no native resources underneath is to be able to
// implement a safe destroy().
if (needsRegistration) {
QRHI_RES_RHI(QRhiGles2);
if (needsRegistration)
rhiD->registerResource(this, false);
}
return true;
}
void QGles2SwapChainTimestamps::prepare(QRhiGles2 *rhiD)
{
rhiD->f->glGenQueries(TIMESTAMP_PAIRS * 2, query);
}
void QGles2SwapChainTimestamps::destroy(QRhiGles2 *rhiD)
{
rhiD->f->glDeleteQueries(TIMESTAMP_PAIRS * 2, query);
}
bool QGles2SwapChainTimestamps::tryQueryTimestamps(int pairIndex, QRhiGles2 *rhiD, double *elapsedSec)
{
if (!active[pairIndex])
return false;
GLuint tsStart = query[pairIndex * 2];
GLuint tsEnd = query[pairIndex * 2 + 1];
GLuint ready = GL_FALSE;
rhiD->f->glGetQueryObjectuiv(tsEnd, GL_QUERY_RESULT_AVAILABLE, &ready);
if (!ready)
return false;
bool result = false;
quint64 timestamps[2];
rhiD->glGetQueryObjectui64v(tsStart, GL_QUERY_RESULT, &timestamps[0]);
rhiD->glGetQueryObjectui64v(tsEnd, GL_QUERY_RESULT, &timestamps[1]);
if (timestamps[1] >= timestamps[0]) {
const quint64 nanoseconds = timestamps[1] - timestamps[0];
*elapsedSec = nanoseconds / 1000000000.0;
result = true;
}
active[pairIndex] = false;
return result;
}
QT_END_NAMESPACE

View File

@ -27,6 +27,7 @@
QT_BEGIN_NAMESPACE
class QOpenGLExtensions;
class QRhiGles2;
struct QGles2Buffer : public QRhiBuffer
{
@ -343,6 +344,12 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
// QRhi*/QGles2* references should be kept at minimum (so no
// QRhiTexture/Buffer/etc. pointers).
union Args {
struct {
GLuint timestampQuery;
} beginFrame;
struct {
GLuint timestampQuery;
} endFrame;
struct {
float x, y, w, h;
float d0, d1;
@ -544,6 +551,7 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
PassType recordingPass;
bool passNeedsResourceTracking;
double lastGpuTime = 0;
QRhiRenderTarget *currentTarget;
QRhiGraphicsPipeline *currentGraphicsPipeline;
QRhiComputePipeline *currentComputePipeline;
@ -639,6 +647,7 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
void resetState() {
recordingPass = NoPass;
passNeedsResourceTracking = true;
// do not zero lastGpuTime
currentTarget = nullptr;
resetCommands();
resetCachedState();
@ -700,6 +709,18 @@ inline bool operator!=(const QGles2CommandBuffer::GraphicsPassState::Blend &a,
return !(a == b);
}
struct QGles2SwapChainTimestamps
{
static const int TIMESTAMP_PAIRS = 2;
bool active[TIMESTAMP_PAIRS] = {};
GLuint query[TIMESTAMP_PAIRS * 2] = {};
void prepare(QRhiGles2 *rhiD);
void destroy(QRhiGles2 *rhiD);
bool tryQueryTimestamps(int pairIndex, QRhiGles2 *rhiD, double *elapsedSec);
};
struct QGles2SwapChain : public QRhiSwapChain
{
QGles2SwapChain(QRhiImplementation *rhi);
@ -725,6 +746,8 @@ struct QGles2SwapChain : public QRhiSwapChain
QGles2SwapChainRenderTarget rtRight;
QGles2CommandBuffer cb;
int frameCount = 0;
QGles2SwapChainTimestamps timestamps;
int currentTimestampPairIndex = 0;
};
class QRhiGles2 : public QRhiImplementation
@ -924,6 +947,8 @@ public:
GLint) = nullptr;
void(QOPENGLF_APIENTRYP glFramebufferTextureMultiviewOVR)(GLenum, GLenum, GLuint, GLint,
GLint, GLsizei) = nullptr;
void (QOPENGLF_APIENTRYP glQueryCounter)(GLuint, GLenum) = nullptr;
void (QOPENGLF_APIENTRYP glGetQueryObjectui64v)(GLuint, GLenum, quint64 *) = nullptr;
uint vao = 0;
struct Caps {
@ -978,7 +1003,8 @@ public:
texture1D(false),
hasDrawBuffersFunc(false),
halfAttributes(false),
multiView(false)
multiView(false),
timestamps(false)
{ }
int ctxMajor;
int ctxMinor;
@ -1033,6 +1059,7 @@ public:
uint hasDrawBuffersFunc : 1;
uint halfAttributes : 1;
uint multiView : 1;
uint timestamps : 1;
} caps;
QGles2SwapChain *currentSwapChain = nullptr;
QSet<GLint> supportedCompressedFormats;
@ -1075,6 +1102,7 @@ public:
OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
bool active = false;
QGles2CommandBuffer cbWrapper;
GLuint tsQueries[2] = {};
} ofr;
QHash<QRhiShaderStage, uint> m_shaderCache;