diff options
Diffstat (limited to 'src/gui/rhi/qrhigles2.cpp')
-rw-r--r-- | src/gui/rhi/qrhigles2.cpp | 945 |
1 files changed, 802 insertions, 143 deletions
diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index a9bb8473a2..dcaa87a5ff 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -1,12 +1,13 @@ -// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include "qrhigles2_p_p.h" +#include "qrhigles2_p.h" #include <QOffscreenSurface> #include <QOpenGLContext> #include <QtCore/qmap.h> #include <QtGui/private/qopenglextensions_p.h> #include <QtGui/private/qopenglprogrambinarycache_p.h> +#include <QtGui/private/qwindow_p.h> #include <qpa/qplatformopenglcontext.h> #include <qmath.h> @@ -27,10 +28,13 @@ QT_BEGIN_NAMESPACE /*! \class QRhiGles2InitParams - \internal \inmodule QtGui + \since 6.6 \brief OpenGL specific initialization parameters. + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + An OpenGL-based QRhi needs an already created QSurface that can be used in combination with QOpenGLContext. Most commonly, this is a QOffscreenSurface in practice. Additionally, while optional, it is recommended that the QWindow @@ -104,12 +108,50 @@ QT_BEGIN_NAMESPACE */ /*! + \variable QRhiGles2InitParams::format + + The QSurfaceFormat, initialized to QSurfaceFormat::defaultFormat() by default. +*/ + +/*! + \variable QRhiGles2InitParams::fallbackSurface + + A QSurface compatible with \l format. Typically a QOffscreenSurface. + Providing this is mandatory. Be aware of the threading implications: a + QOffscreenSurface, like QWindow, must only ever be created and destroyed on + the main (gui) thread, even if the QRhi is created and operates on another + thread. +*/ + +/*! + \variable QRhiGles2InitParams::window + + Optional, but setting it is recommended when targeting a QWindow with the + QRhi. +*/ + +/*! + \variable QRhiGles2InitParams::shareContext + + Optional, the QOpenGLContext to share resource with. QRhi creates its own + context, and setting this member to a valid QOpenGLContext leads to calling + \l{QOpenGLContext::setShareContext()}{setShareContext()} with it. +*/ + +/*! \class QRhiGles2NativeHandles - \internal \inmodule QtGui + \since 6.6 \brief Holds the OpenGL context used by the QRhi. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ +/*! + \variable QRhiGles2NativeHandles::context +*/ + #ifndef GL_BGRA #define GL_BGRA 0x80E1 #endif @@ -174,6 +216,10 @@ QT_BEGIN_NAMESPACE #define GL_DEPTH_COMPONENT32F 0x8CAC #endif +#ifndef GL_UNSIGNED_INT_24_8 +#define GL_UNSIGNED_INT_24_8 0x84FA +#endif + #ifndef GL_STENCIL_INDEX #define GL_STENCIL_INDEX 0x1901 #endif @@ -199,7 +245,7 @@ QT_BEGIN_NAMESPACE #endif #ifndef GL_FRAMEBUFFER_SRGB -#define GL_FRAMEBUFFER_SRGB 0x8DB9 +#define GL_FRAMEBUFFER_SRGB 0x8DB9 #endif #ifndef GL_READ_FRAMEBUFFER @@ -314,6 +360,10 @@ QT_BEGIN_NAMESPACE #define GL_TEXTURE_2D_MULTISAMPLE 0x9100 #endif +#ifndef GL_TEXTURE_2D_MULTISAMPLE_ARRAY +#define GL_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9102 +#endif + #ifndef GL_TEXTURE_EXTERNAL_OES #define GL_TEXTURE_EXTERNAL_OES 0x8D65 #endif @@ -443,15 +493,39 @@ 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 + +#ifndef GL_BUFFER +#define GL_BUFFER 0x82E0 +#endif + +#ifndef GL_PROGRAM +#define GL_PROGRAM 0x82E2 #endif /*! @@ -658,6 +732,8 @@ bool QRhiGles2::create(QRhi::Flags flags) return false; f = static_cast<QOpenGLExtensions *>(ctx->extraFunctions()); + const QSurfaceFormat actualFormat = ctx->format(); + caps.gles = actualFormat.renderableType() == QSurfaceFormat::OpenGLES; if (!caps.gles) { glPolygonMode = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLenum)>( @@ -708,8 +784,6 @@ bool QRhiGles2::create(QRhi::Flags flags) if (version) driverInfoStruct.deviceName += QByteArray(version); - const QSurfaceFormat actualFormat = ctx->format(); - caps.ctxMajor = actualFormat.majorVersion(); caps.ctxMinor = actualFormat.minorVersion(); @@ -769,8 +843,6 @@ bool QRhiGles2::create(QRhi::Flags flags) f->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &caps.maxTextureSize); - caps.gles = actualFormat.renderableType() == QSurfaceFormat::OpenGLES; - if (!caps.gles || caps.ctxMajor >= 3) { // non-ES or ES 3.0+ f->glGetIntegerv(GL_MAX_DRAW_BUFFERS, &caps.maxDrawBuffers); @@ -782,8 +854,8 @@ bool QRhiGles2::create(QRhi::Flags flags) caps.maxDrawBuffers = 1; caps.hasDrawBuffersFunc = false; // This does not mean MSAA is not supported, just that we cannot query - // the supported sample counts. - caps.maxSamples = 1; + // the supported sample counts. Assume that 4x is always supported. + caps.maxSamples = 4; } caps.msaaRenderBuffer = f->hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample) @@ -820,7 +892,13 @@ bool QRhiGles2::create(QRhi::Flags flags) #else caps.needsDepthStencilCombinedAttach = false; #endif - caps.srgbCapableDefaultFramebuffer = f->hasOpenGLExtension(QOpenGLExtensions::SRGBFrameBuffer); + + // QOpenGLExtensions::SRGBFrameBuffer is not useful here. We need to know if + // controlling the sRGB-on-shader-write state is supported, not that if the + // default framebuffer is sRGB-capable. And there are two different + // extensions for desktop and ES. + caps.srgbWriteControl = ctx->hasExtension("GL_EXT_framebuffer_sRGB") || ctx->hasExtension("GL_EXT_sRGB_write_control"); + caps.coreProfile = actualFormat.profile() == QSurfaceFormat::CoreProfile; if (caps.gles) @@ -941,7 +1019,7 @@ bool QRhiGles2::create(QRhi::Flags flags) f->glGetIntegerv(GL_MAX_VARYING_VECTORS, &caps.maxVertexOutputs); } else if (caps.ctxMajor >= 3) { GLint components = 0; - f->glGetIntegerv(GL_MAX_VARYING_COMPONENTS, &components); + f->glGetIntegerv(caps.coreProfile ? GL_MAX_VERTEX_OUTPUT_COMPONENTS : GL_MAX_VARYING_COMPONENTS, &components); caps.maxVertexOutputs = components / 4; } else { // OpenGL before 3.0 only has this, and not the same as @@ -954,7 +1032,8 @@ bool QRhiGles2::create(QRhi::Flags flags) if (!caps.gles) { f->glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); - f->glEnable(GL_POINT_SPRITE); + if (!caps.coreProfile) + f->glEnable(GL_POINT_SPRITE); } // else (with gles) these are always on // Match D3D and others when it comes to seamless cubemap filtering. @@ -965,6 +1044,60 @@ bool QRhiGles2::create(QRhi::Flags flags) caps.halfAttributes = f->hasOpenGLExtension(QOpenGLExtensions::HalfFloatVertex); + // We always require GL_OVR_multiview2 for symmetry with other backends. + caps.multiView = f->hasOpenGLExtension(QOpenGLExtensions::MultiView) + && f->hasOpenGLExtension(QOpenGLExtensions::MultiViewExtended); + if (caps.multiView) { + glFramebufferTextureMultiviewOVR = + reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLenum, GLuint, GLint, GLint, GLsizei)>( + 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; + } + + // glObjectLabel is available on OpenGL ES 3.2+ and OpenGL 4.3+ + if (caps.gles) + caps.objectLabel = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2); + else + caps.objectLabel = caps.ctxMajor > 4 || (caps.ctxMajor == 4 && caps.ctxMinor >= 3); + if (caps.objectLabel) { + glObjectLabel = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLuint, GLsizei, const GLchar *)>( + ctx->getProcAddress(QByteArrayLiteral("glObjectLabel"))); + } + + if (caps.gles) { + // This is the third way to get multisample rendering with GLES. (1. is + // multisample render buffer -> resolve to texture; 2. is multisample + // texture with GLES 3.1; 3. is this, avoiding the explicit multisample + // buffer and should be more efficient with tiled architectures. + // Interesting also because 2. does not seem to work in practice on + // devices such as the Quest 3) + caps.glesMultisampleRenderToTexture = ctx->hasExtension("GL_EXT_multisampled_render_to_texture"); + if (caps.glesMultisampleRenderToTexture) { + glFramebufferTexture2DMultisampleEXT = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLenum, GLenum, GLuint, GLint, GLsizei)>( + ctx->getProcAddress(QByteArrayLiteral("glFramebufferTexture2DMultisampleEXT"))); + } + caps.glesMultiviewMultisampleRenderToTexture = ctx->hasExtension("GL_OVR_multiview_multisampled_render_to_texture"); + if (caps.glesMultiviewMultisampleRenderToTexture) { + glFramebufferTextureMultisampleMultiviewOVR = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLenum, GLuint, GLint, GLsizei, GLint, GLsizei)>( + ctx->getProcAddress(QByteArrayLiteral("glFramebufferTextureMultisampleMultiviewOVR"))); + } + } else { + caps.glesMultisampleRenderToTexture = false; + caps.glesMultiviewMultisampleRenderToTexture = false; + } + + caps.unpackRowLength = !caps.gles || caps.ctxMajor >= 3; + nativeHandlesStruct.context = ctx; contextLost = false; @@ -980,6 +1113,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; @@ -1017,6 +1155,7 @@ void QRhiGles2::executeDeferredReleases() break; case QRhiGles2::DeferredReleaseEntry::TextureRenderTarget: f->glDeleteFramebuffers(1, &e.textureRenderTarget.framebuffer); + f->glDeleteTextures(1, &e.textureRenderTarget.nonMsaaThrowawayDepthTexture); break; default: Q_UNREACHABLE(); @@ -1036,17 +1175,6 @@ QList<int> QRhiGles2::supportedSampleCounts() const return supportedSampleCountList; } -int QRhiGles2::effectiveSampleCount(int sampleCount) const -{ - // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1. - const int s = qBound(1, sampleCount, 64); - if (!supportedSampleCounts().contains(s)) { - qWarning("Attempted to set unsupported sample count %d", sampleCount); - return 1; - } - return s; -} - QRhiSwapChain *QRhiGles2::createSwapChain() { return new QGles2SwapChain(this); @@ -1171,13 +1299,13 @@ static inline void toGlTextureFormat(QRhiTexture::Format format, const QRhiGles2 *glintformat = GL_DEPTH_COMPONENT24; *glsizedintformat = *glintformat; *glformat = GL_DEPTH_COMPONENT; - *gltype = GL_UNSIGNED_SHORT; + *gltype = GL_UNSIGNED_INT; break; case QRhiTexture::D24S8: *glintformat = GL_DEPTH24_STENCIL8; *glsizedintformat = *glintformat; *glformat = GL_DEPTH_STENCIL; - *gltype = GL_UNSIGNED_SHORT; + *gltype = GL_UNSIGNED_INT_24_8; break; case QRhiTexture::D32F: *glintformat = GL_DEPTH_COMPONENT32F; @@ -1254,7 +1382,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: @@ -1300,7 +1428,7 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const case QRhi::PipelineCacheDataLoadSave: return caps.programBinary; case QRhi::ImageDataStride: - return !caps.gles || caps.ctxMajor >= 3; + return caps.unpackRowLength; case QRhi::RenderBufferImport: return true; case QRhi::ThreeDimensionalTextures: @@ -1327,6 +1455,12 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const return caps.texture1D; case QRhi::ThreeDimensionalTextureMipmaps: return caps.texture3D; + case QRhi::MultiView: + return caps.multiView && caps.maxTextureArraySize > 0; + case QRhi::TextureViewFormat: + return false; + case QRhi::ResolveDepthStencil: + return true; default: Q_UNREACHABLE_RETURN(false); } @@ -1901,10 +2035,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) @@ -1964,6 +2102,12 @@ void QRhiGles2::endExternal(QRhiCommandBuffer *cb) enqueueBindFramebuffer(cbD->currentTarget, cbD); } +double QRhiGles2::lastCompletedGpuTime(QRhiCommandBuffer *cb) +{ + QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); + return cbD->lastGpuTime; +} + QRhi::FrameOpResult QRhiGles2::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags) { QGles2SwapChain *swapChainD = QRHI_RES(QGles2SwapChain, swapChain); @@ -1977,7 +2121,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; } @@ -1987,7 +2141,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; @@ -2019,7 +2181,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; @@ -2031,7 +2198,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; @@ -2044,6 +2211,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, ×tamps[1]); + glGetQueryObjectui64v(ofr.tsQueries[0], GL_QUERY_RESULT, ×tamps[0]); + if (timestamps[1] >= timestamps[0]) { + const quint64 nanoseconds = timestamps[1] - timestamps[0]; + ofr.cbWrapper.lastGpuTime = nanoseconds / 1000000000.0; // seconds + } + } + return QRhi::FrameOpSuccess; } @@ -2163,17 +2340,15 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb const GLenum effectiveTarget = faceTargetBase + (isCubeMap ? uint(layer) : 0u); const QPoint dp = subresDesc.destinationTopLeft(); const QByteArray rawData = subresDesc.data(); - if (!subresDesc.image().isNull()) { - QImage img = subresDesc.image(); - QSize size = img.size(); + + auto setCmdByNotCompressedData = [&](const void* data, QSize size, quint32 dataStride) + { + quint32 bytesPerLine = 0; + quint32 bytesPerPixel = 0; + textureFormatInfo(texD->m_format, size, &bytesPerLine, nullptr, &bytesPerPixel); + QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::SubImage; - if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) { - const QPoint sp = subresDesc.sourceTopLeft(); - if (!subresDesc.sourceSize().isEmpty()) - size = subresDesc.sourceSize(); - img = img.copy(sp.x(), sp.y(), size.width(), size.height()); - } cmd.args.subImage.target = texD->target; cmd.args.subImage.texture = texD->texture; cmd.args.subImage.faceTarget = effectiveTarget; @@ -2185,10 +2360,38 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb cmd.args.subImage.h = size.height(); cmd.args.subImage.glformat = texD->glformat; cmd.args.subImage.gltype = texD->gltype; - cmd.args.subImage.rowStartAlign = 4; - cmd.args.subImage.rowLength = 0; - cmd.args.subImage.data = cbD->retainImage(img); + + if (dataStride == 0) + dataStride = bytesPerLine; + + cmd.args.subImage.rowStartAlign = (dataStride & 3) ? 1 : 4; + cmd.args.subImage.rowLength = caps.unpackRowLength ? (bytesPerPixel ? dataStride / bytesPerPixel : 0) : 0; + + cmd.args.subImage.data = data; + }; + + if (!subresDesc.image().isNull()) { + QImage img = subresDesc.image(); + QSize size = img.size(); + if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) { + const QPoint sp = subresDesc.sourceTopLeft(); + if (!subresDesc.sourceSize().isEmpty()) + size = subresDesc.sourceSize(); + + if (caps.unpackRowLength) { + cbD->retainImage(img); + // create a non-owning wrapper for the subimage + const uchar *data = img.constBits() + sp.y() * img.bytesPerLine() + sp.x() * (qMax(1, img.depth() / 8)); + img = QImage(data, size.width(), size.height(), img.bytesPerLine(), img.format()); + } else { + img = img.copy(sp.x(), sp.y(), size.width(), size.height()); + } + } + + setCmdByNotCompressedData(cbD->retainImage(img), size, img.bytesPerLine()); } else if (!rawData.isEmpty() && isCompressed) { + const int depth = qMax(1, texD->m_depth); + const int arraySize = qMax(0, texD->m_arraySize); if ((texD->flags().testFlag(QRhiTexture::UsedAsCompressedAtlas) || is3D || isArray) && !texD->zeroInitialized) { @@ -2200,9 +2403,9 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb quint32 byteSize = 0; compressedFormatInfo(texD->m_format, texD->m_pixelSize, nullptr, &byteSize, nullptr); if (is3D) - byteSize *= texD->m_depth; + byteSize *= depth; if (isArray) - byteSize *= texD->m_arraySize; + byteSize *= arraySize; QByteArray zeroBuf(byteSize, 0); QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::CompressedImage; @@ -2212,9 +2415,8 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb cmd.args.compressedImage.level = level; cmd.args.compressedImage.glintformat = texD->glintformat; cmd.args.compressedImage.w = texD->m_pixelSize.width(); - cmd.args.compressedImage.h = - is1D && isArray ? texD->m_arraySize : texD->m_pixelSize.height(); - cmd.args.compressedImage.depth = is3D ? texD->m_depth : (isArray ? texD->m_arraySize : 0); + cmd.args.compressedImage.h = is1D && isArray ? arraySize : texD->m_pixelSize.height(); + cmd.args.compressedImage.depth = is3D ? depth : (isArray ? arraySize : 0); cmd.args.compressedImage.size = byteSize; cmd.args.compressedImage.data = cbD->retainData(zeroBuf); texD->zeroInitialized = true; @@ -2246,39 +2448,16 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb cmd.args.compressedImage.level = level; cmd.args.compressedImage.glintformat = texD->glintformat; cmd.args.compressedImage.w = size.width(); - cmd.args.compressedImage.h = is1D && isArray ? texD->m_arraySize : size.height(); - cmd.args.compressedImage.depth = is3D ? texD->m_depth : (isArray ? texD->m_arraySize : 0); + cmd.args.compressedImage.h = is1D && isArray ? arraySize : size.height(); + cmd.args.compressedImage.depth = is3D ? depth : (isArray ? arraySize : 0); cmd.args.compressedImage.size = rawData.size(); cmd.args.compressedImage.data = cbD->retainData(rawData); } } else if (!rawData.isEmpty()) { const QSize size = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize) : subresDesc.sourceSize(); - quint32 bytesPerLine = 0; - quint32 bytesPerPixel = 0; - textureFormatInfo(texD->m_format, size, &bytesPerLine, nullptr, &bytesPerPixel); - QGles2CommandBuffer::Command &cmd(cbD->commands.get()); - cmd.cmd = QGles2CommandBuffer::Command::SubImage; - cmd.args.subImage.target = texD->target; - cmd.args.subImage.texture = texD->texture; - cmd.args.subImage.faceTarget = effectiveTarget; - cmd.args.subImage.level = level; - cmd.args.subImage.dx = dp.x(); - cmd.args.subImage.dy = is1D && isArray ? layer : dp.y(); - cmd.args.subImage.dz = is3D || isArray ? layer : 0; - cmd.args.subImage.w = size.width(); - cmd.args.subImage.h = size.height(); - cmd.args.subImage.glformat = texD->glformat; - cmd.args.subImage.gltype = texD->gltype; - // Default unpack alignment (row start alignment - // requirement) is 4. QImage guarantees 4 byte aligned - // row starts, but our raw data here does not. - cmd.args.subImage.rowStartAlign = (bytesPerLine & 3) ? 1 : 4; - if (subresDesc.dataStride() && bytesPerPixel) - cmd.args.subImage.rowLength = subresDesc.dataStride() / bytesPerPixel; - else - cmd.args.subImage.rowLength = 0; - cmd.args.subImage.data = cbD->retainData(rawData); + + setCmdByNotCompressedData(cbD->retainData(rawData), size, subresDesc.dataStride()); } else { qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level); } @@ -2797,6 +2976,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); @@ -2823,6 +3004,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) @@ -2967,6 +3150,38 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) type = GL_HALF_FLOAT; size = 1; break; + case QRhiVertexInputAttribute::UShort4: + type = GL_UNSIGNED_SHORT; + size = 4; + break; + case QRhiVertexInputAttribute::UShort3: + type = GL_UNSIGNED_SHORT; + size = 3; + break; + case QRhiVertexInputAttribute::UShort2: + type = GL_UNSIGNED_SHORT; + size = 2; + break; + case QRhiVertexInputAttribute::UShort: + type = GL_UNSIGNED_SHORT; + size = 1; + break; + case QRhiVertexInputAttribute::SShort4: + type = GL_SHORT; + size = 4; + break; + case QRhiVertexInputAttribute::SShort3: + type = GL_SHORT; + size = 3; + break; + case QRhiVertexInputAttribute::SShort2: + type = GL_SHORT; + size = 2; + break; + case QRhiVertexInputAttribute::SShort: + type = GL_SHORT; + size = 1; + break; default: break; } @@ -3109,7 +3324,7 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) } if (caps.hasDrawBuffersFunc) f->glDrawBuffers(bufs.count(), bufs.constData()); - if (caps.srgbCapableDefaultFramebuffer) { + if (caps.srgbWriteControl) { if (cmd.args.bindFramebuffer.srgb) f->glEnable(GL_FRAMEBUFFER_SRGB); else @@ -3141,7 +3356,7 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) break; case QGles2CommandBuffer::Command::GetBufferSubData: { - QRhiBufferReadbackResult *result = cmd.args.getBufferSubData.result; + QRhiReadbackResult *result = cmd.args.getBufferSubData.result; bindVertexIndexBufferWithStateReset(&state, f, cmd.args.getBufferSubData.target, cmd.args.getBufferSubData.buffer); if (caps.gles) { if (caps.properMapBuffer) { @@ -3264,6 +3479,14 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) result->data.resize(w * h * 8); f->glReadPixels(0, 0, w, h, GL_RGBA, GL_HALF_FLOAT, result->data.data()); break; + case QRhiTexture::R16F: + result->data.resize(w * h * 2); + f->glReadPixels(0, 0, w, h, GL_RED, GL_HALF_FLOAT, result->data.data()); + break; + case QRhiTexture::R32F: + result->data.resize(w * h * 4); + f->glReadPixels(0, 0, w, h, GL_RED, GL_FLOAT, result->data.data()); + break; case QRhiTexture::RGBA32F: result->data.resize(w * h * 16); f->glReadPixels(0, 0, w, h, GL_RGBA, GL_FLOAT, result->data.data()); @@ -3362,23 +3585,128 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) break; case QGles2CommandBuffer::Command::BlitFromRenderbuffer: { + // Altering the scissor state, so reset the stored state, although + // not strictly required as long as blit is done in endPass() only. + cbD->graphicsPassState.reset(); + f->glDisable(GL_SCISSOR_TEST); GLuint fbo[2]; f->glGenFramebuffers(2, fbo); f->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[0]); - f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_RENDERBUFFER, cmd.args.blitFromRb.renderbuffer); + const bool ds = cmd.args.blitFromRenderbuffer.isDepthStencil; + if (ds) { + f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + GL_RENDERBUFFER, cmd.args.blitFromRenderbuffer.renderbuffer); + f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, cmd.args.blitFromRenderbuffer.renderbuffer); + } else { + f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, cmd.args.blitFromRenderbuffer.renderbuffer); + } f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]); - if (cmd.args.blitFromRb.target == GL_TEXTURE_3D || cmd.args.blitFromRb.target == GL_TEXTURE_2D_ARRAY) { - f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - cmd.args.blitFromRb.texture, cmd.args.blitFromRb.dstLevel, cmd.args.blitFromRb.dstLayer); + if (cmd.args.blitFromRenderbuffer.target == GL_TEXTURE_3D || cmd.args.blitFromRenderbuffer.target == GL_TEXTURE_2D_ARRAY) { + if (ds) { + f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + cmd.args.blitFromRenderbuffer.dstTexture, + cmd.args.blitFromRenderbuffer.dstLevel, + cmd.args.blitFromRenderbuffer.dstLayer); + f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + cmd.args.blitFromRenderbuffer.dstTexture, + cmd.args.blitFromRenderbuffer.dstLevel, + cmd.args.blitFromRenderbuffer.dstLayer); + } else { + f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + cmd.args.blitFromRenderbuffer.dstTexture, + cmd.args.blitFromRenderbuffer.dstLevel, + cmd.args.blitFromRenderbuffer.dstLayer); + } } else { - f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.blitFromRb.target, - cmd.args.blitFromRb.texture, cmd.args.blitFromRb.dstLevel); + if (ds) { + f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, cmd.args.blitFromRenderbuffer.target, + cmd.args.blitFromRenderbuffer.dstTexture, cmd.args.blitFromRenderbuffer.dstLevel); + f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, cmd.args.blitFromRenderbuffer.target, + cmd.args.blitFromRenderbuffer.dstTexture, cmd.args.blitFromRenderbuffer.dstLevel); + } else { + f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.blitFromRenderbuffer.target, + cmd.args.blitFromRenderbuffer.dstTexture, cmd.args.blitFromRenderbuffer.dstLevel); + } } - f->glBlitFramebuffer(0, 0, cmd.args.blitFromRb.w, cmd.args.blitFromRb.h, - 0, 0, cmd.args.blitFromRb.w, cmd.args.blitFromRb.h, - GL_COLOR_BUFFER_BIT, - GL_LINEAR); + f->glBlitFramebuffer(0, 0, cmd.args.blitFromRenderbuffer.w, cmd.args.blitFromRenderbuffer.h, + 0, 0, cmd.args.blitFromRenderbuffer.w, cmd.args.blitFromRenderbuffer.h, + ds ? GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT : GL_COLOR_BUFFER_BIT, + GL_NEAREST); // Qt 5 used Nearest when resolving samples, stick to that + f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject()); + f->glDeleteFramebuffers(2, fbo); + } + break; + case QGles2CommandBuffer::Command::BlitFromTexture: + { + // Altering the scissor state, so reset the stored state, although + // not strictly required as long as blit is done in endPass() only. + cbD->graphicsPassState.reset(); + f->glDisable(GL_SCISSOR_TEST); + GLuint fbo[2]; + f->glGenFramebuffers(2, fbo); + f->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[0]); + const bool ds = cmd.args.blitFromTexture.isDepthStencil; + if (cmd.args.blitFromTexture.srcTarget == GL_TEXTURE_2D_MULTISAMPLE_ARRAY) { + if (ds) { + f->glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + cmd.args.blitFromTexture.srcTexture, + cmd.args.blitFromTexture.srcLevel, + cmd.args.blitFromTexture.srcLayer); + f->glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + cmd.args.blitFromTexture.srcTexture, + cmd.args.blitFromTexture.srcLevel, + cmd.args.blitFromTexture.srcLayer); + } else { + f->glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + cmd.args.blitFromTexture.srcTexture, + cmd.args.blitFromTexture.srcLevel, + cmd.args.blitFromTexture.srcLayer); + } + } else { + if (ds) { + f->glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, cmd.args.blitFromTexture.srcTarget, + cmd.args.blitFromTexture.srcTexture, cmd.args.blitFromTexture.srcLevel); + f->glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, cmd.args.blitFromTexture.srcTarget, + cmd.args.blitFromTexture.srcTexture, cmd.args.blitFromTexture.srcLevel); + } else { + f->glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.blitFromTexture.srcTarget, + cmd.args.blitFromTexture.srcTexture, cmd.args.blitFromTexture.srcLevel); + } + } + f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]); + if (cmd.args.blitFromTexture.dstTarget == GL_TEXTURE_3D || cmd.args.blitFromTexture.dstTarget == GL_TEXTURE_2D_ARRAY) { + if (ds) { + f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + cmd.args.blitFromTexture.dstTexture, + cmd.args.blitFromTexture.dstLevel, + cmd.args.blitFromTexture.dstLayer); + f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + cmd.args.blitFromTexture.dstTexture, + cmd.args.blitFromTexture.dstLevel, + cmd.args.blitFromTexture.dstLayer); + } else { + f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + cmd.args.blitFromTexture.dstTexture, + cmd.args.blitFromTexture.dstLevel, + cmd.args.blitFromTexture.dstLayer); + } + } else { + if (ds) { + f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, cmd.args.blitFromTexture.dstTarget, + cmd.args.blitFromTexture.dstTexture, cmd.args.blitFromTexture.dstLevel); + f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, cmd.args.blitFromTexture.dstTarget, + cmd.args.blitFromTexture.dstTexture, cmd.args.blitFromTexture.dstLevel); + } else { + f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.blitFromTexture.dstTarget, + cmd.args.blitFromTexture.dstTexture, cmd.args.blitFromTexture.dstLevel); + } + } + f->glBlitFramebuffer(0, 0, cmd.args.blitFromTexture.w, cmd.args.blitFromTexture.h, + 0, 0, cmd.args.blitFromTexture.w, cmd.args.blitFromTexture.h, + ds ? GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT : GL_COLOR_BUFFER_BIT, + GL_NEAREST); // Qt 5 used Nearest when resolving samples, stick to that f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject()); f->glDeleteFramebuffers(2, fbo); } @@ -3427,6 +3755,13 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) if (caps.compute) f->glMemoryBarrier(cmd.args.barrier.barriers); break; + case QGles2CommandBuffer::Command::InvalidateFramebuffer: + if (caps.gles && caps.ctxMajor >= 3) { + f->glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, + cmd.args.invalidateFramebuffer.attCount, + cmd.args.invalidateFramebuffer.att); + } + break; default: break; } @@ -4225,32 +4560,129 @@ void QRhiGles2::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resource if (cbD->currentTarget->resourceType() == QRhiResource::TextureRenderTarget) { QGles2TextureRenderTarget *rtTex = QRHI_RES(QGles2TextureRenderTarget, cbD->currentTarget); - if (rtTex->m_desc.cbeginColorAttachments() != rtTex->m_desc.cendColorAttachments()) { - // handle only 1 color attachment and only (msaa) renderbuffer - const QRhiColorAttachment &colorAtt(*rtTex->m_desc.cbeginColorAttachments()); - if (colorAtt.resolveTexture()) { - Q_ASSERT(colorAtt.renderBuffer()); + for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments(); + it != itEnd; ++it) + { + const QRhiColorAttachment &colorAtt(*it); + if (!colorAtt.resolveTexture()) + continue; + + QGles2Texture *resolveTexD = QRHI_RES(QGles2Texture, colorAtt.resolveTexture()); + const QSize size = resolveTexD->pixelSize(); + if (colorAtt.renderBuffer()) { QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, colorAtt.renderBuffer()); - const QSize size = colorAtt.resolveTexture()->pixelSize(); if (rbD->pixelSize() != size) { qWarning("Resolve source (%dx%d) and target (%dx%d) size does not match", rbD->pixelSize().width(), rbD->pixelSize().height(), size.width(), size.height()); } QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::BlitFromRenderbuffer; - cmd.args.blitFromRb.renderbuffer = rbD->renderbuffer; - cmd.args.blitFromRb.w = size.width(); - cmd.args.blitFromRb.h = size.height(); - QGles2Texture *colorTexD = QRHI_RES(QGles2Texture, colorAtt.resolveTexture()); - if (colorTexD->m_flags.testFlag(QRhiTexture::CubeMap)) - cmd.args.blitFromRb.target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + uint(colorAtt.resolveLayer()); + cmd.args.blitFromRenderbuffer.renderbuffer = rbD->renderbuffer; + cmd.args.blitFromRenderbuffer.w = size.width(); + cmd.args.blitFromRenderbuffer.h = size.height(); + if (resolveTexD->m_flags.testFlag(QRhiTexture::CubeMap)) + cmd.args.blitFromRenderbuffer.target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + uint(colorAtt.resolveLayer()); else - cmd.args.blitFromRb.target = colorTexD->target; - cmd.args.blitFromRb.texture = colorTexD->texture; - cmd.args.blitFromRb.dstLevel = colorAtt.resolveLevel(); - const bool hasZ = colorTexD->m_flags.testFlag(QRhiTexture::ThreeDimensional) - || colorTexD->m_flags.testFlag(QRhiTexture::TextureArray); - cmd.args.blitFromRb.dstLayer = hasZ ? colorAtt.resolveLayer() : 0; + cmd.args.blitFromRenderbuffer.target = resolveTexD->target; + cmd.args.blitFromRenderbuffer.dstTexture = resolveTexD->texture; + cmd.args.blitFromRenderbuffer.dstLevel = colorAtt.resolveLevel(); + const bool hasZ = resolveTexD->m_flags.testFlag(QRhiTexture::ThreeDimensional) + || resolveTexD->m_flags.testFlag(QRhiTexture::TextureArray); + cmd.args.blitFromRenderbuffer.dstLayer = hasZ ? colorAtt.resolveLayer() : 0; + cmd.args.blitFromRenderbuffer.isDepthStencil = false; + } else if (caps.glesMultisampleRenderToTexture) { + // Nothing to do, resolving into colorAtt.resolveTexture() is automatic, + // colorAtt.texture() is in fact not used for anything. + } else { + Q_ASSERT(colorAtt.texture()); + QGles2Texture *texD = QRHI_RES(QGles2Texture, colorAtt.texture()); + if (texD->pixelSize() != size) { + qWarning("Resolve source (%dx%d) and target (%dx%d) size does not match", + texD->pixelSize().width(), texD->pixelSize().height(), size.width(), size.height()); + } + const int resolveCount = colorAtt.multiViewCount() >= 2 ? colorAtt.multiViewCount() : 1; + for (int resolveIdx = 0; resolveIdx < resolveCount; ++resolveIdx) { + const int srcLayer = colorAtt.layer() + resolveIdx; + const int dstLayer = colorAtt.resolveLayer() + resolveIdx; + QGles2CommandBuffer::Command &cmd(cbD->commands.get()); + cmd.cmd = QGles2CommandBuffer::Command::BlitFromTexture; + if (texD->m_flags.testFlag(QRhiTexture::CubeMap)) + cmd.args.blitFromTexture.srcTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + uint(srcLayer); + else + cmd.args.blitFromTexture.srcTarget = texD->target; + cmd.args.blitFromTexture.srcTexture = texD->texture; + cmd.args.blitFromTexture.srcLevel = colorAtt.level(); + cmd.args.blitFromTexture.srcLayer = 0; + if (texD->m_flags.testFlag(QRhiTexture::ThreeDimensional) || texD->m_flags.testFlag(QRhiTexture::TextureArray)) + cmd.args.blitFromTexture.srcLayer = srcLayer; + cmd.args.blitFromTexture.w = size.width(); + cmd.args.blitFromTexture.h = size.height(); + if (resolveTexD->m_flags.testFlag(QRhiTexture::CubeMap)) + cmd.args.blitFromTexture.dstTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + uint(dstLayer); + else + cmd.args.blitFromTexture.dstTarget = resolveTexD->target; + cmd.args.blitFromTexture.dstTexture = resolveTexD->texture; + cmd.args.blitFromTexture.dstLevel = colorAtt.resolveLevel(); + cmd.args.blitFromTexture.dstLayer = 0; + if (resolveTexD->m_flags.testFlag(QRhiTexture::ThreeDimensional) || resolveTexD->m_flags.testFlag(QRhiTexture::TextureArray)) + cmd.args.blitFromTexture.dstLayer = dstLayer; + cmd.args.blitFromTexture.isDepthStencil = false; + } + } + } + + if (rtTex->m_desc.depthResolveTexture()) { + QGles2Texture *depthResolveTexD = QRHI_RES(QGles2Texture, rtTex->m_desc.depthResolveTexture()); + const QSize size = depthResolveTexD->pixelSize(); + if (rtTex->m_desc.depthStencilBuffer()) { + QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, rtTex->m_desc.depthStencilBuffer()); + QGles2CommandBuffer::Command &cmd(cbD->commands.get()); + cmd.cmd = QGles2CommandBuffer::Command::BlitFromRenderbuffer; + cmd.args.blitFromRenderbuffer.renderbuffer = rbD->renderbuffer; + cmd.args.blitFromRenderbuffer.w = size.width(); + cmd.args.blitFromRenderbuffer.h = size.height(); + cmd.args.blitFromRenderbuffer.target = depthResolveTexD->target; + cmd.args.blitFromRenderbuffer.dstTexture = depthResolveTexD->texture; + cmd.args.blitFromRenderbuffer.dstLevel = 0; + cmd.args.blitFromRenderbuffer.dstLayer = 0; + cmd.args.blitFromRenderbuffer.isDepthStencil = true; + } else if (caps.glesMultisampleRenderToTexture) { + // Nothing to do, resolving into depthResolveTexture() is automatic. + } else { + QGles2Texture *depthTexD = QRHI_RES(QGles2Texture, rtTex->m_desc.depthTexture()); + const int resolveCount = depthTexD->arraySize() >= 2 ? depthTexD->arraySize() : 1; + for (int resolveIdx = 0; resolveIdx < resolveCount; ++resolveIdx) { + QGles2CommandBuffer::Command &cmd(cbD->commands.get()); + cmd.cmd = QGles2CommandBuffer::Command::BlitFromTexture; + cmd.args.blitFromTexture.srcTarget = depthTexD->target; + cmd.args.blitFromTexture.srcTexture = depthTexD->texture; + cmd.args.blitFromTexture.srcLevel = 0; + cmd.args.blitFromTexture.srcLayer = resolveIdx; + cmd.args.blitFromTexture.w = size.width(); + cmd.args.blitFromTexture.h = size.height(); + cmd.args.blitFromTexture.dstTarget = depthResolveTexD->target; + cmd.args.blitFromTexture.dstTexture = depthResolveTexD->texture; + cmd.args.blitFromTexture.dstLevel = 0; + cmd.args.blitFromTexture.dstLayer = resolveIdx; + cmd.args.blitFromTexture.isDepthStencil = true; + } + } + } + + const bool mayDiscardDepthStencil = + (rtTex->m_desc.depthStencilBuffer() + || (rtTex->m_desc.depthTexture() && rtTex->m_flags.testFlag(QRhiTextureRenderTarget::DoNotStoreDepthStencilContents))) + && !rtTex->m_desc.depthResolveTexture(); + if (mayDiscardDepthStencil) { + QGles2CommandBuffer::Command &cmd(cbD->commands.get()); + cmd.cmd = QGles2CommandBuffer::Command::InvalidateFramebuffer; + if (caps.needsDepthStencilCombinedAttach) { + cmd.args.invalidateFramebuffer.attCount = 1; + cmd.args.invalidateFramebuffer.att[0] = GL_DEPTH_STENCIL_ATTACHMENT; + } else { + cmd.args.invalidateFramebuffer.attCount = 2; + cmd.args.invalidateFramebuffer.att[0] = GL_DEPTH_ATTACHMENT; + cmd.args.invalidateFramebuffer.att[1] = GL_STENCIL_ATTACHMENT; } } } @@ -4885,6 +5317,9 @@ bool QGles2Buffer::create() rhiD->f->glBindBuffer(targetForDataOps, buffer); rhiD->f->glBufferData(targetForDataOps, nonZeroSize, nullptr, m_type == Dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); + if (rhiD->glObjectLabel) + rhiD->glObjectLabel(GL_BUFFER, buffer, -1, m_objectName.constData()); + usageState.access = AccessNone; rhiD->registerResource(this); @@ -5039,6 +5474,9 @@ bool QGles2RenderBuffer::create() break; } + if (rhiD->glObjectLabel) + rhiD->glObjectLabel(GL_RENDERBUFFER, renderbuffer, -1, m_objectName.constData()); + owns = true; generation += 1; rhiD->registerResource(this); @@ -5155,12 +5593,10 @@ bool QGles2Texture::prepareCreate(QSize *adjustedSize) return false; } - m_depth = qMax(1, m_depth); if (m_depth > 1 && !is3D) { qWarning("Texture cannot have a depth of %d when it is not 3D", m_depth); return false; } - m_arraySize = qMax(0, m_arraySize); if (m_arraySize > 0 && !isArray) { qWarning("Texture cannot have an array size of %d when it is not an array", m_arraySize); return false; @@ -5171,7 +5607,7 @@ bool QGles2Texture::prepareCreate(QSize *adjustedSize) } target = isCube ? GL_TEXTURE_CUBE_MAP - : m_sampleCount > 1 ? GL_TEXTURE_2D_MULTISAMPLE + : m_sampleCount > 1 ? (isArray ? GL_TEXTURE_2D_MULTISAMPLE_ARRAY : GL_TEXTURE_2D_MULTISAMPLE) : (is3D ? GL_TEXTURE_3D : (is1D ? (isArray ? GL_TEXTURE_1D_ARRAY : GL_TEXTURE_1D) : (isArray ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D))); @@ -5235,13 +5671,13 @@ bool QGles2Texture::create() const QSize mipSize = rhiD->q->sizeForMipLevel(level, size); if (isArray) rhiD->f->glTexImage2D(target, level, GLint(glintformat), mipSize.width(), - m_arraySize, 0, glformat, gltype, nullptr); + qMax(0, m_arraySize), 0, glformat, gltype, nullptr); else rhiD->glTexImage1D(target, level, GLint(glintformat), mipSize.width(), 0, glformat, gltype, nullptr); } } else if (is3D || isArray) { - const int layerCount = is3D ? m_depth : m_arraySize; + const int layerCount = is3D ? qMax(1, m_depth) : qMax(0, m_arraySize); if (hasMipMaps) { for (int level = 0; level != mipLevelCount; ++level) { const QSize mipSize = rhiD->q->sizeForMipLevel(level, size); @@ -5263,8 +5699,16 @@ bool QGles2Texture::create() } } } else { - rhiD->f->glTexImage2D(target, 0, GLint(glintformat), size.width(), size.height(), - 0, glformat, gltype, nullptr); + // 2D texture. For multisample textures the GLES 3.1 + // glStorage2DMultisample must be used for portability. + if (m_sampleCount > 1 && rhiD->caps.multisampledTexture) { + // internal format must be sized + rhiD->f->glTexStorage2DMultisample(target, m_sampleCount, glsizedintformat, + size.width(), size.height(), GL_TRUE); + } else { + rhiD->f->glTexImage2D(target, 0, GLint(glintformat), size.width(), size.height(), + 0, glformat, gltype, nullptr); + } } } else { // Must be specified with immutable storage functions otherwise @@ -5273,10 +5717,14 @@ bool QGles2Texture::create() if (is1D && !isArray) rhiD->glTexStorage1D(target, mipLevelCount, glsizedintformat, size.width()); else if (!is1D && (is3D || isArray)) - rhiD->f->glTexStorage3D(target, mipLevelCount, glsizedintformat, size.width(), size.height(), is3D ? m_depth : m_arraySize); + rhiD->f->glTexStorage3D(target, mipLevelCount, glsizedintformat, size.width(), size.height(), + is3D ? qMax(1, m_depth) : qMax(0, m_arraySize)); + else if (m_sampleCount > 1) + rhiD->f->glTexStorage2DMultisample(target, m_sampleCount, glsizedintformat, + size.width(), size.height(), GL_TRUE); else rhiD->f->glTexStorage2D(target, mipLevelCount, glsizedintformat, size.width(), - is1D ? m_arraySize : size.height()); + is1D ? qMax(0, m_arraySize) : size.height()); } specified = true; } else { @@ -5286,6 +5734,9 @@ bool QGles2Texture::create() specified = false; } + if (rhiD->glObjectLabel) + rhiD->glObjectLabel(GL_TEXTURE, texture, -1, m_objectName.constData()); + owns = true; generation += 1; @@ -5332,7 +5783,9 @@ QGles2Sampler::~QGles2Sampler() void QGles2Sampler::destroy() { - // nothing to do here + QRHI_RES_RHI(QRhiGles2); + if (rhiD) + rhiD->unregisterResource(this); } bool QGles2Sampler::create() @@ -5345,6 +5798,8 @@ bool QGles2Sampler::create() d.gltexcomparefunc = toGlTextureCompareFunc(m_compareOp); generation += 1; + QRHI_RES_RHI(QRhiGles2); + rhiD->registerResource(this, false); return true; } @@ -5361,7 +5816,9 @@ QGles2RenderPassDescriptor::~QGles2RenderPassDescriptor() void QGles2RenderPassDescriptor::destroy() { - // nothing to do here + QRHI_RES_RHI(QRhiGles2); + if (rhiD) + rhiD->unregisterResource(this); } bool QGles2RenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const @@ -5372,7 +5829,10 @@ bool QGles2RenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *ot QRhiRenderPassDescriptor *QGles2RenderPassDescriptor::newCompatibleRenderPassDescriptor() const { - return new QGles2RenderPassDescriptor(m_rhi); + QGles2RenderPassDescriptor *rpD = new QGles2RenderPassDescriptor(m_rhi); + QRHI_RES_RHI(QRhiGles2); + rhiD->registerResource(rpD, false); + return rpD; } QVector<quint32> QGles2RenderPassDescriptor::serializedFormat() const @@ -5433,8 +5893,10 @@ void QGles2TextureRenderTarget::destroy() e.type = QRhiGles2::DeferredReleaseEntry::TextureRenderTarget; e.textureRenderTarget.framebuffer = framebuffer; + e.textureRenderTarget.nonMsaaThrowawayDepthTexture = nonMsaaThrowawayDepthTexture; framebuffer = 0; + nonMsaaThrowawayDepthTexture = 0; QRHI_RES_RHI(QRhiGles2); if (rhiD) { @@ -5445,7 +5907,10 @@ void QGles2TextureRenderTarget::destroy() QRhiRenderPassDescriptor *QGles2TextureRenderTarget::newCompatibleRenderPassDescriptor() { - return new QGles2RenderPassDescriptor(m_rhi); + QGles2RenderPassDescriptor *rpD = new QGles2RenderPassDescriptor(m_rhi); + QRHI_RES_RHI(QRhiGles2); + rhiD->registerResource(rpD, false); + return rpD; } bool QGles2TextureRenderTarget::create() @@ -5478,6 +5943,7 @@ bool QGles2TextureRenderTarget::create() d.colorAttCount = 0; int attIndex = 0; + int multiViewCount = 0; for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) { d.colorAttCount += 1; const QRhiColorAttachment &colorAtt(*it); @@ -5488,20 +5954,56 @@ bool QGles2TextureRenderTarget::create() QGles2Texture *texD = QRHI_RES(QGles2Texture, texture); Q_ASSERT(texD->texture && texD->specified); if (texD->flags().testFlag(QRhiTexture::ThreeDimensional) || texD->flags().testFlag(QRhiTexture::TextureArray)) { - rhiD->f->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), texD->texture, - colorAtt.level(), colorAtt.layer()); + if (colorAtt.multiViewCount() < 2) { + rhiD->f->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), texD->texture, + colorAtt.level(), colorAtt.layer()); + } else { + multiViewCount = colorAtt.multiViewCount(); + if (texD->sampleCount() > 1 && rhiD->caps.glesMultiviewMultisampleRenderToTexture && colorAtt.resolveTexture()) { + // Special path for GLES and GL_OVR_multiview_multisampled_render_to_texture: + // ignore the color attachment's (multisample) texture + // array and give the resolve texture array to GL. (no + // explicit resolving is needed by us later on) + QGles2Texture *resolveTexD = QRHI_RES(QGles2Texture, colorAtt.resolveTexture()); + rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0 + uint(attIndex), + resolveTexD->texture, + colorAtt.resolveLevel(), + texD->sampleCount(), + colorAtt.resolveLayer(), + multiViewCount); + } else { + rhiD->glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0 + uint(attIndex), + texD->texture, + colorAtt.level(), + colorAtt.layer(), + multiViewCount); + } + } } else if (texD->flags().testFlag(QRhiTexture::OneDimensional)) { rhiD->glFramebufferTexture1D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), texD->target + uint(colorAtt.layer()), texD->texture, colorAtt.level()); } else { - const GLenum faceTargetBase = texD->flags().testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target; - rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), faceTargetBase + uint(colorAtt.layer()), - texD->texture, colorAtt.level()); + if (texD->sampleCount() > 1 && rhiD->caps.glesMultisampleRenderToTexture && colorAtt.resolveTexture()) { + // Special path for GLES and GL_EXT_multisampled_render_to_texture: + // ignore the color attachment's (multisample) texture and + // give the resolve texture to GL. (no explicit resolving is + // needed by us later on) + QGles2Texture *resolveTexD = QRHI_RES(QGles2Texture, colorAtt.resolveTexture()); + const GLenum faceTargetBase = resolveTexD->flags().testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : resolveTexD->target; + rhiD->glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), faceTargetBase + uint(colorAtt.resolveLayer()), + resolveTexD->texture, colorAtt.level(), texD->sampleCount()); + } else { + const GLenum faceTargetBase = texD->flags().testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target; + rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), faceTargetBase + uint(colorAtt.layer()), + texD->texture, colorAtt.level()); + } } if (attIndex == 0) { d.pixelSize = rhiD->q->sizeForMipLevel(colorAtt.level(), texD->pixelSize()); - d.sampleCount = 1; + d.sampleCount = texD->sampleCount(); } } else if (renderBuffer) { QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, renderBuffer); @@ -5522,12 +6024,14 @@ bool QGles2TextureRenderTarget::create() } else { rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRbD->renderbuffer); - if (depthRbD->stencilRenderbuffer) + if (depthRbD->stencilRenderbuffer) { rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthRbD->stencilRenderbuffer); - else // packed + } else { + // packed depth-stencil rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthRbD->renderbuffer); + } } if (d.colorAttCount == 0) { d.pixelSize = depthRbD->pixelSize(); @@ -5535,11 +6039,105 @@ bool QGles2TextureRenderTarget::create() } } else { QGles2Texture *depthTexD = QRHI_RES(QGles2Texture, m_desc.depthTexture()); - rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexD->target, - depthTexD->texture, 0); + if (multiViewCount < 2) { + if (depthTexD->sampleCount() > 1 && rhiD->caps.glesMultisampleRenderToTexture && m_desc.depthResolveTexture()) { + // Special path for GLES and + // GL_EXT_multisampled_render_to_texture, for depth-stencil. + // Relevant only when depthResolveTexture is set. + QGles2Texture *depthResolveTexD = QRHI_RES(QGles2Texture, m_desc.depthResolveTexture()); + rhiD->glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthResolveTexD->target, + depthResolveTexD->texture, 0, depthTexD->sampleCount()); + if (rhiD->isStencilSupportingFormat(depthResolveTexD->format())) { + rhiD->glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, depthResolveTexD->target, + depthResolveTexD->texture, 0, depthTexD->sampleCount()); + } + } else { + rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexD->target, + depthTexD->texture, 0); + if (rhiD->isStencilSupportingFormat(depthTexD->format())) { + rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, depthTexD->target, + depthTexD->texture, 0); + } + } + } else { + if (depthTexD->sampleCount() > 1 && rhiD->caps.glesMultiviewMultisampleRenderToTexture) { + // And so it turns out + // https://registry.khronos.org/OpenGL/extensions/OVR/OVR_multiview.txt + // does not work with multisample 2D texture arrays. (at least + // that's what Issue 30 in the extension spec seems to imply) + // + // There is https://registry.khronos.org/OpenGL/extensions/EXT/EXT_multiview_texture_multisample.txt + // that seems to resolve that, but that does not seem to + // work (or not available) on GLES devices such as the Quest 3. + // + // So instead, on GLES we can use the + // multisample-multiview-auto-resolving version (which in + // turn is not supported on desktop GL e.g. by NVIDIA), too + // bad we have a multisample depth texture array here as + // every other API out there requires that. So, in absence + // of a depthResolveTexture, create a temporary one ignoring + // what the user has already created. + // + if (!m_flags.testFlag(DoNotStoreDepthStencilContents) && !m_desc.depthResolveTexture()) { + qWarning("Attempted to create a multiview+multisample QRhiTextureRenderTarget, but DoNotStoreDepthStencilContents was not set." + " This path has no choice but to behave as if DoNotStoreDepthStencilContents was set, because QRhi is forced to create" + " a throwaway non-multisample depth texture here. Set the flag to silence this warning, or set a depthResolveTexture."); + } + if (m_desc.depthResolveTexture()) { + QGles2Texture *depthResolveTexD = QRHI_RES(QGles2Texture, m_desc.depthResolveTexture()); + rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, + GL_DEPTH_ATTACHMENT, + depthResolveTexD->texture, + 0, + depthTexD->sampleCount(), + 0, + multiViewCount); + if (rhiD->isStencilSupportingFormat(depthResolveTexD->format())) { + rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, + GL_STENCIL_ATTACHMENT, + depthResolveTexD->texture, + 0, + depthTexD->sampleCount(), + 0, + multiViewCount); + } + } else { + if (!nonMsaaThrowawayDepthTexture) { + rhiD->f->glGenTextures(1, &nonMsaaThrowawayDepthTexture); + rhiD->f->glBindTexture(GL_TEXTURE_2D_ARRAY, nonMsaaThrowawayDepthTexture); + rhiD->f->glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_DEPTH24_STENCIL8, + depthTexD->pixelSize().width(), depthTexD->pixelSize().height(), multiViewCount); + } + rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, + GL_DEPTH_ATTACHMENT, + nonMsaaThrowawayDepthTexture, + 0, + depthTexD->sampleCount(), + 0, + multiViewCount); + rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, + GL_STENCIL_ATTACHMENT, + nonMsaaThrowawayDepthTexture, + 0, + depthTexD->sampleCount(), + 0, + multiViewCount); + } + } else { + // The depth texture here must be an array with at least + // multiViewCount elements, and the format should be D24 or D32F + // for depth only, or D24S8 for depth and stencil. + rhiD->glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexD->texture, + 0, 0, multiViewCount); + if (rhiD->isStencilSupportingFormat(depthTexD->format())) { + rhiD->glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, depthTexD->texture, + 0, 0, multiViewCount); + } + } + } if (d.colorAttCount == 0) { d.pixelSize = depthTexD->pixelSize(); - d.sampleCount = 1; + d.sampleCount = depthTexD->sampleCount(); } } d.dsAttCount = 1; @@ -5556,6 +6154,9 @@ bool QGles2TextureRenderTarget::create() return false; } + if (rhiD->glObjectLabel) + rhiD->glObjectLabel(GL_FRAMEBUFFER, framebuffer, -1, m_objectName.constData()); + QRhiRenderTargetAttachmentTracker::updateResIdList<QGles2Texture, QGles2RenderBuffer>(m_desc, &d.currentResIdList); rhiD->registerResource(this); @@ -5592,7 +6193,9 @@ QGles2ShaderResourceBindings::~QGles2ShaderResourceBindings() void QGles2ShaderResourceBindings::destroy() { - // nothing to do here + QRHI_RES_RHI(QRhiGles2); + if (rhiD) + rhiD->unregisterResource(this); } bool QGles2ShaderResourceBindings::create() @@ -5615,6 +6218,7 @@ bool QGles2ShaderResourceBindings::create() rhiD->updateLayoutDesc(this); generation += 1; + rhiD->registerResource(this, false); return true; } @@ -5799,6 +6403,9 @@ bool QGles2GraphicsPipeline::create() currentSrb = nullptr; currentSrbGeneration = 0; + if (rhiD->glObjectLabel) + rhiD->glObjectLabel(GL_PROGRAM, program, -1, m_objectName.constData()); + rhiD->pipelineCreationEnd(); generation += 1; rhiD->registerResource(this); @@ -5969,7 +6576,12 @@ QRhiRenderTarget *QGles2SwapChain::currentFrameRenderTarget(StereoTargetBuffer t QSize QGles2SwapChain::surfacePixelSize() { Q_ASSERT(m_window); - return m_window->size() * m_window->devicePixelRatio(); + if (QPlatformWindow *platformWindow = m_window->handle()) + // Prefer using QPlatformWindow geometry and DPR in order to avoid + // errors due to rounded QWindow geometry. + return platformWindow->geometry().size() * platformWindow->devicePixelRatio(); + else + return m_window->size() * m_window->devicePixelRatio(); } bool QGles2SwapChain::isFormatSupported(Format f) @@ -5979,7 +6591,10 @@ bool QGles2SwapChain::isFormatSupported(Format f) QRhiRenderPassDescriptor *QGles2SwapChain::newCompatibleRenderPassDescriptor() { - return new QGles2RenderPassDescriptor(m_rhi); + QGles2RenderPassDescriptor *rpD = new QGles2RenderPassDescriptor(m_rhi); + QRHI_RES_RHI(QRhiGles2); + rhiD->registerResource(rpD, false); + return rpD; } void QGles2SwapChain::initSwapChainRenderTarget(QGles2SwapChainRenderTarget *rt) @@ -6023,15 +6638,59 @@ 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); - rhiD->registerResource(this); - } + if (needsRegistration) + rhiD->registerResource(this, false); return true; } +void QGles2SwapChainTimestamps::prepare(QRhiGles2 *rhiD) +{ + if (!query[0]) + rhiD->f->glGenQueries(TIMESTAMP_PAIRS * 2, query); +} + +void QGles2SwapChainTimestamps::destroy(QRhiGles2 *rhiD) +{ + rhiD->f->glDeleteQueries(TIMESTAMP_PAIRS * 2, query); + memset(active, 0, sizeof(active)); + memset(query, 0, sizeof(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, ×tamps[0]); + rhiD->glGetQueryObjectui64v(tsEnd, GL_QUERY_RESULT, ×tamps[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 |