diff options
Diffstat (limited to 'src/gui/rhi/qrhigles2.cpp')
-rw-r--r-- | src/gui/rhi/qrhigles2.cpp | 836 |
1 files changed, 719 insertions, 117 deletions
diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index 37e3373c21..4f1aceea48 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -7,6 +7,7 @@ #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> @@ -31,7 +32,7 @@ QT_BEGIN_NAMESPACE \since 6.6 \brief OpenGL specific initialization parameters. - \note This a RHI API with limited compatibility guarantees, see \l QRhi + \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 @@ -143,7 +144,7 @@ QT_BEGIN_NAMESPACE \since 6.6 \brief Holds the OpenGL context used by the QRhi. - \note This a RHI API with limited compatibility guarantees, see \l QRhi + \note This is a RHI API with limited compatibility guarantees, see \l QRhi for details. */ @@ -215,6 +216,18 @@ QT_BEGIN_NAMESPACE #define GL_DEPTH_COMPONENT32F 0x8CAC #endif +#ifndef GL_DEPTH32F_STENCIL8 +#define GL_DEPTH32F_STENCIL8 0x8CAD +#endif + +#ifndef GL_FLOAT_32_UNSIGNED_INT_24_8_REV +#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD +#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 @@ -240,7 +253,7 @@ QT_BEGIN_NAMESPACE #endif #ifndef GL_FRAMEBUFFER_SRGB -#define GL_FRAMEBUFFER_SRGB 0x8DB9 +#define GL_FRAMEBUFFER_SRGB 0x8DB9 #endif #ifndef GL_READ_FRAMEBUFFER @@ -355,6 +368,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 @@ -484,21 +501,41 @@ 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 + /*! Constructs a new QRhiGles2InitParams. @@ -825,8 +862,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) @@ -863,7 +900,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) @@ -1009,6 +1052,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; @@ -1024,6 +1121,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; @@ -1061,6 +1163,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(); @@ -1080,17 +1183,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); @@ -1215,13 +1307,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; @@ -1229,6 +1321,12 @@ static inline void toGlTextureFormat(QRhiTexture::Format format, const QRhiGles2 *glformat = GL_DEPTH_COMPONENT; *gltype = GL_FLOAT; break; + case QRhiTexture::D32FS8: + *glintformat = GL_DEPTH32F_STENCIL8; + *glsizedintformat = *glintformat; + *glformat = GL_DEPTH_STENCIL; + *gltype = GL_FLOAT_32_UNSIGNED_INT_24_8_REV; + break; default: Q_UNREACHABLE(); *glintformat = GL_RGBA; @@ -1247,6 +1345,7 @@ bool QRhiGles2::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture switch (format) { case QRhiTexture::D16: case QRhiTexture::D32F: + case QRhiTexture::D32FS8: return caps.depthTexture; case QRhiTexture::D24: @@ -1298,7 +1397,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: @@ -1344,7 +1443,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: @@ -1371,6 +1470,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); } @@ -1945,10 +2050,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) @@ -2010,8 +2119,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) @@ -2027,7 +2136,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; } @@ -2037,7 +2156,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; @@ -2069,7 +2196,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; @@ -2081,7 +2213,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; @@ -2094,6 +2226,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; } @@ -2213,17 +2355,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; @@ -2235,9 +2375,35 @@ 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); @@ -2305,31 +2471,8 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb } 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); } @@ -2848,6 +2991,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); @@ -2874,6 +3019,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) @@ -3018,6 +3165,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; } @@ -3160,7 +3339,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 @@ -3315,6 +3494,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()); @@ -3420,20 +3607,121 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) 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); } @@ -3482,6 +3770,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; } @@ -3737,7 +4032,7 @@ void QRhiGles2::bindCombinedSampler(QGles2CommandBuffer *cbD, QGles2Texture *tex f->glTexParameteri(texD->target, GL_TEXTURE_MAG_FILTER, GLint(samplerD->d.glmagfilter)); f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_S, GLint(samplerD->d.glwraps)); f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_T, GLint(samplerD->d.glwrapt)); - if (caps.texture3D) + if (caps.texture3D && texD->target == GL_TEXTURE_3D) f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_R, GLint(samplerD->d.glwrapr)); if (caps.textureCompareMode) { if (samplerD->d.gltexcomparefunc != GL_NEVER) { @@ -4280,32 +4575,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; } } } @@ -4940,6 +5332,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); @@ -5094,6 +5489,9 @@ bool QGles2RenderBuffer::create() break; } + if (rhiD->glObjectLabel) + rhiD->glObjectLabel(GL_RENDERBUFFER, renderbuffer, -1, m_objectName.constData()); + owns = true; generation += 1; rhiD->registerResource(this); @@ -5224,7 +5622,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))); @@ -5316,8 +5714,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 @@ -5328,6 +5734,9 @@ bool QGles2Texture::create() else if (!is1D && (is3D || isArray)) 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 ? qMax(0, m_arraySize) : size.height()); @@ -5340,6 +5749,9 @@ bool QGles2Texture::create() specified = false; } + if (rhiD->glObjectLabel) + rhiD->glObjectLabel(GL_TEXTURE, texture, -1, m_objectName.constData()); + owns = true; generation += 1; @@ -5496,8 +5908,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) { @@ -5544,6 +5958,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); @@ -5554,20 +5969,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); @@ -5588,12 +6039,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(); @@ -5601,11 +6054,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; @@ -5622,6 +6169,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); @@ -5868,6 +6418,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); @@ -6038,7 +6591,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) @@ -6095,15 +6653,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); + 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 |