diff options
Diffstat (limited to 'src/gui/rhi/qrhigles2.cpp')
-rw-r--r-- | src/gui/rhi/qrhigles2.cpp | 1642 |
1 files changed, 1294 insertions, 348 deletions
diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index 0c2225f194..dcaa87a5ff 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -1,48 +1,13 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Gui module -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qrhigles2_p_p.h" -#include <QWindow> +// 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.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> @@ -63,13 +28,17 @@ QT_BEGIN_NAMESPACE /*! \class QRhiGles2InitParams - \internal \inmodule QtGui + \since 6.6 \brief OpenGL specific initialization parameters. - An OpenGL-based QRhi needs an already created QOffscreenSurface at minimum. - Additionally, while optional, it is recommended that the QWindow the first - QRhiSwapChain will target is passed in as well. + \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 + the first QRhiSwapChain will target is passed in as well. \badcode QOffscreenSurface *fallbackSurface = QRhiGles2InitParams::newFallbackSurface(); @@ -86,21 +55,21 @@ QT_BEGIN_NAMESPACE thread) are satisfied. The implicitly created context is destroyed automatically together with the QRhi. - The QSurfaceFormat for the context is specified in \l format. The + The QSurfaceFormat for the context is specified in \c format. The constructor sets this to QSurfaceFormat::defaultFormat() so applications - that use QSurfaceFormat::setDefaultFormat() do not need to set the format - again. + that call QSurfaceFormat::setDefaultFormat() with the appropriate settings + before the constructor runs will not need to change value of \c format. - \note The depth and stencil buffer sizes are set automatically to 24 and 8 - when no size was explicitly set for these buffers in \l format. As there - are possible adjustments to \l format, applications can use - adjustedFormat() to query the effective format that is passed to - QOpenGLContext::setFormat() internally. + \note Remember to set the depth and stencil buffer sizes to 24 and 8 when + the renderer relies on depth or stencil testing, either in the global + default QSurfaceFormat, or, alternatively, separately in all the involved + QSurfaceFormat instances: in \c format, the format argument passed to + newFallbackSurface(), and on any QWindow that is used with the QRhi. - A QOffscreenSurface has to be specified in \l fallbackSurface. In order to - prevent mistakes in threaded situations, this is never created - automatically by the QRhi since, like QWindow, QOffscreenSurface can only - be created on the gui/main thread. + A QSurface has to be specified in \c fallbackSurface. In order to prevent + mistakes in threaded situations, this is never created automatically by the + QRhi because, like QWindow, instances of QSurface subclasses can often be + created on the gui/main thread only. As a convenience, applications can use newFallbackSurface() which creates and returns a QOffscreenSurface that is compatible with the QOpenGLContext @@ -112,14 +81,14 @@ QT_BEGIN_NAMESPACE instances that have their surface type set to QSurface::OpenGLSurface or QSurface::RasterGLSurface. - \note \l window is optional. It is recommended to specify it whenever + \note \c window is optional. It is recommended to specify it whenever possible, in order to avoid problems on multi-adapter and multi-screen - systems. When \l window is not set, the very first - QOpenGLContext::makeCurrent() happens with \l fallbackSurface which may be + systems. When \c window is not set, the very first + QOpenGLContext::makeCurrent() happens with \c fallbackSurface which may be an invisible window on some platforms (for example, Windows) and that may trigger unexpected problems in some cases. - In case resource sharing with an existing QOpenGLContext is desired, \l + In case resource sharing with an existing QOpenGLContext is desired, \c shareContext can be set to an existing QOpenGLContext. Alternatively, Qt::AA_ShareOpenGLContexts is honored as well, when enabled. @@ -128,8 +97,7 @@ QT_BEGIN_NAMESPACE When interoperating with another graphics engine, it may be necessary to get a QRhi instance that uses the same OpenGL context. This can be achieved by passing a pointer to a QRhiGles2NativeHandles to QRhi::create(). The - \l{QRhiGles2NativeHandles::context}{context} must be set to a non-null - value. + \c{QRhiGles2NativeHandles::context} must be set to a non-null value then. An alternative approach is to create a QOpenGLContext that \l{QOpenGLContext::setShareContext()}{shares resources} with the other @@ -140,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 @@ -210,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 @@ -235,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 @@ -350,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 @@ -454,10 +468,66 @@ QT_BEGIN_NAMESPACE #define GL_PATCH_VERTICES 0x8E72 #endif +#ifndef GL_LINE +#define GL_LINE 0x1B01 +#endif + +#ifndef GL_FILL +#define GL_FILL 0x1B02 +#endif + #ifndef GL_PATCHES #define GL_PATCHES 0x000E #endif +#ifndef GL_GEOMETRY_SHADER +#define GL_GEOMETRY_SHADER 0x8DD9 +#endif + +#ifndef GL_BACK_LEFT +#define GL_BACK_LEFT 0x0402 +#endif + +#ifndef GL_BACK_RIGHT +#define GL_BACK_RIGHT 0x0403 +#endif + +#ifndef GL_TEXTURE_1D +# define GL_TEXTURE_1D 0x0DE0 +#endif + +#ifndef GL_TEXTURE_1D_ARRAY +# define GL_TEXTURE_1D_ARRAY 0x8C18 +#endif + +#ifndef GL_HALF_FLOAT +#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. @@ -469,28 +539,12 @@ QRhiGles2InitParams::QRhiGles2InitParams() } /*! - \return the QSurfaceFormat that will be set on the QOpenGLContext before - calling QOpenGLContext::create(). This format is based on \a format, but - may be adjusted. Applicable only when QRhi creates the context. - Applications are advised to set this format on their QWindow in order to - avoid potential BAD_MATCH failures. - */ -QSurfaceFormat QRhiGles2InitParams::adjustedFormat(const QSurfaceFormat &format) -{ - QSurfaceFormat fmt = format; - - if (fmt.depthBufferSize() == -1) - fmt.setDepthBufferSize(24); - if (fmt.stencilBufferSize() == -1) - fmt.setStencilBufferSize(8); - - return fmt; -} - -/*! \return a new QOffscreenSurface that can be used with a QRhi by passing it via a QRhiGles2InitParams. + When \a format is not specified, its default value is the global default + format settable via QSurfaceFormat::setDefaultFormat(). + \a format is adjusted as appropriate in order to avoid having problems afterwards due to an incompatible context and surface. @@ -502,7 +556,7 @@ QSurfaceFormat QRhiGles2InitParams::adjustedFormat(const QSurfaceFormat &format) */ QOffscreenSurface *QRhiGles2InitParams::newFallbackSurface(const QSurfaceFormat &format) { - QSurfaceFormat fmt = adjustedFormat(format); + QSurfaceFormat fmt = format; // To resolve all fields in the format as much as possible, create a context. // This may be heavy, but allows avoiding BAD_MATCH on some systems. @@ -523,7 +577,7 @@ QOffscreenSurface *QRhiGles2InitParams::newFallbackSurface(const QSurfaceFormat QRhiGles2::QRhiGles2(QRhiGles2InitParams *params, QRhiGles2NativeHandles *importDevice) : ofr(this) { - requestedFormat = QRhiGles2InitParams::adjustedFormat(params->format); + requestedFormat = params->format; fallbackSurface = params->fallbackSurface; maybeWindow = params->window; // may be null maybeShareContext = params->shareContext; // may be null @@ -553,15 +607,32 @@ static inline QSurface *currentSurfaceForCurrentContext(QOpenGLContext *ctx) return currentSurface; } +QSurface *QRhiGles2::evaluateFallbackSurface() const +{ + // With Apple's deprecated OpenGL support we need to minimize the usage of + // QOffscreenSurface since delicate problems can pop up with + // NSOpenGLContext and drawables. +#if defined(Q_OS_MACOS) + return maybeWindow && maybeWindow->handle() ? static_cast<QSurface *>(maybeWindow) : fallbackSurface; +#else + return fallbackSurface; +#endif +} + bool QRhiGles2::ensureContext(QSurface *surface) const { if (!surface) { + // null means any surface is good because not going to render if (currentSurfaceForCurrentContext(ctx)) return true; - surface = fallbackSurface; + // if the context is not already current with a valid surface, use our + // fallback surface, but platform specific quirks may apply + surface = evaluateFallbackSurface(); } else if (surface->surfaceClass() == QSurface::Window && !surface->surfaceHandle()) { - surface = fallbackSurface; + // the window is not usable anymore (no native window underneath), behave as if offscreen + surface = evaluateFallbackSurface(); } else if (!needsMakeCurrentDueToSwap && currentSurfaceForCurrentContext(ctx) == surface) { + // bail out if the makeCurrent is not necessary return true; } needsMakeCurrentDueToSwap = false; @@ -661,6 +732,40 @@ 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)>( + ctx->getProcAddress(QByteArrayLiteral("glPolygonMode"))); + + glTexImage1D = reinterpret_cast<void(QOPENGLF_APIENTRYP)( + GLenum, GLint, GLint, GLsizei, GLint, GLenum, GLenum, const void *)>( + ctx->getProcAddress(QByteArrayLiteral("glTexImage1D"))); + + glTexStorage1D = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLint, GLenum, GLsizei)>( + ctx->getProcAddress(QByteArrayLiteral("glTexStorage1D"))); + + glTexSubImage1D = reinterpret_cast<void(QOPENGLF_APIENTRYP)( + GLenum, GLint, GLint, GLsizei, GLenum, GLenum, const GLvoid *)>( + ctx->getProcAddress(QByteArrayLiteral("glTexSubImage1D"))); + + glCopyTexSubImage1D = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLint, GLint, GLint, + GLint, GLsizei)>( + ctx->getProcAddress(QByteArrayLiteral("glCopyTexSubImage1D"))); + + glCompressedTexImage1D = reinterpret_cast<void(QOPENGLF_APIENTRYP)( + GLenum, GLint, GLenum, GLsizei, GLint, GLsizei, const GLvoid *)>( + ctx->getProcAddress(QByteArrayLiteral("glCompressedTexImage1D"))); + + glCompressedTexSubImage1D = reinterpret_cast<void(QOPENGLF_APIENTRYP)( + GLenum, GLint, GLint, GLsizei, GLenum, GLsizei, const GLvoid *)>( + ctx->getProcAddress(QByteArrayLiteral("glCompressedTexSubImage1D"))); + + glFramebufferTexture1D = + reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLenum, GLenum, GLuint, GLint)>( + ctx->getProcAddress(QByteArrayLiteral("glFramebufferTexture1D"))); + } const char *vendor = reinterpret_cast<const char *>(f->glGetString(GL_VENDOR)); const char *renderer = reinterpret_cast<const char *>(f->glGetString(GL_RENDERER)); @@ -679,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(); @@ -740,13 +843,19 @@ bool QRhiGles2::create(QRhi::Flags flags) f->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &caps.maxTextureSize); - if (caps.ctxMajor >= 3 || actualFormat.renderableType() == QSurfaceFormat::OpenGL) { + if (!caps.gles || caps.ctxMajor >= 3) { + // non-ES or ES 3.0+ f->glGetIntegerv(GL_MAX_DRAW_BUFFERS, &caps.maxDrawBuffers); + caps.hasDrawBuffersFunc = true; f->glGetIntegerv(GL_MAX_SAMPLES, &caps.maxSamples); caps.maxSamples = qMax(1, caps.maxSamples); } else { + // ES 2.0 / WebGL 1 caps.maxDrawBuffers = 1; - caps.maxSamples = 1; + caps.hasDrawBuffersFunc = false; + // This does not mean MSAA is not supported, just that we cannot query + // the supported sample counts. Assume that 4x is always supported. + caps.maxSamples = 4; } caps.msaaRenderBuffer = f->hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample) @@ -755,14 +864,20 @@ bool QRhiGles2::create(QRhi::Flags flags) caps.npotTextureFull = f->hasOpenGLFeature(QOpenGLFunctions::NPOTTextures) && f->hasOpenGLFeature(QOpenGLFunctions::NPOTTextureRepeat); - caps.gles = actualFormat.renderableType() == QSurfaceFormat::OpenGLES; if (caps.gles) caps.fixedIndexPrimitiveRestart = caps.ctxMajor >= 3; // ES 3.0 else caps.fixedIndexPrimitiveRestart = caps.ctxMajor > 4 || (caps.ctxMajor == 4 && caps.ctxMinor >= 3); // 4.3 - if (caps.fixedIndexPrimitiveRestart) + if (caps.fixedIndexPrimitiveRestart) { +#ifdef Q_OS_WASM + // WebGL 2 behaves as if GL_PRIMITIVE_RESTART_FIXED_INDEX was always + // enabled (i.e. matching D3D/Metal), and the value cannot be passed to + // glEnable, so skip the call. +#else f->glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX); +#endif + } caps.bgraExternalFormat = f->hasOpenGLExtension(QOpenGLExtensions::BGRATextureFormat); caps.bgraInternalFormat = caps.bgraExternalFormat && caps.gles; @@ -777,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) @@ -853,10 +974,20 @@ bool QRhiGles2::create(QRhi::Flags flags) caps.texture3D = caps.ctxMajor >= 3; // 3.0 if (caps.gles) + caps.texture1D = false; // ES + else + caps.texture1D = glTexImage1D && (caps.ctxMajor >= 2); // 2.0 + + if (caps.gles) caps.tessellation = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2); // ES 3.2 else caps.tessellation = caps.ctxMajor >= 4; // 4.0 + if (caps.gles) + caps.geometryShader = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2); // ES 3.2 + else + caps.geometryShader = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2); // 3.2 + if (caps.ctxMajor >= 3) { // 3.0 or ES 3.0 GLint maxArraySize = 0; f->glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &maxArraySize); @@ -888,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 @@ -901,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. @@ -910,6 +1042,62 @@ bool QRhiGles2::create(QRhi::Flags flags) if (!caps.gles && (caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2))) f->glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); + 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; @@ -925,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; @@ -944,7 +1137,7 @@ void QRhiGles2::destroy() void QRhiGles2::executeDeferredReleases() { - for (int i = releaseQueue.count() - 1; i >= 0; --i) { + for (int i = releaseQueue.size() - 1; i >= 0; --i) { const QRhiGles2::DeferredReleaseEntry &e(releaseQueue[i]); switch (e.type) { case QRhiGles2::DeferredReleaseEntry::Buffer: @@ -962,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(); @@ -981,23 +1175,12 @@ 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); } -QRhiBuffer *QRhiGles2::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, int size) +QRhiBuffer *QRhiGles2::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size) { return new QGles2Buffer(this, type, usage, size); } @@ -1116,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; @@ -1199,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: @@ -1245,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: @@ -1256,9 +1439,30 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const return caps.maxTextureArraySize > 0; case QRhi::Tessellation: return caps.tessellation; - default: - Q_UNREACHABLE(); + case QRhi::GeometryShader: + return caps.geometryShader; + case QRhi::TextureArrayRange: return false; + case QRhi::NonFillPolygonMode: + return !caps.gles; + case QRhi::OneDimensionalTextures: + return caps.texture1D; + case QRhi::OneDimensionalTextureMipmaps: + return caps.texture1D; + case QRhi::HalfAttributes: + return caps.halfAttributes; + case QRhi::RenderToOneDimensionalTexture: + 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); } } @@ -1296,8 +1500,7 @@ int QRhiGles2::resourceLimit(QRhi::ResourceLimit limit) const case QRhi::MaxVertexOutputs: return caps.maxVertexOutputs; default: - Q_UNREACHABLE(); - return 0; + Q_UNREACHABLE_RETURN(0); } } @@ -1311,9 +1514,11 @@ QRhiDriverInfo QRhiGles2::driverInfo() const return driverInfoStruct; } -QRhiMemAllocStats QRhiGles2::graphicsMemoryAllocationStatistics() +QRhiStats QRhiGles2::statistics() { - return {}; + QRhiStats result; + result.totalPipelineCreationTime = totalPipelineCreationTime(); + return result; } bool QRhiGles2::makeThreadLocalNativeContextCurrent() @@ -1362,8 +1567,8 @@ QByteArray QRhiGles2::pipelineCacheData() memset(&header, 0, sizeof(header)); header.rhiId = pipelineCacheRhiId(); header.arch = quint32(sizeof(void*)); - header.programBinaryCount = m_pipelineCache.count(); - const size_t driverStrLen = qMin(sizeof(header.driver) - 1, size_t(driverInfoStruct.deviceName.count())); + header.programBinaryCount = m_pipelineCache.size(); + const size_t driverStrLen = qMin(sizeof(header.driver) - 1, size_t(driverInfoStruct.deviceName.size())); if (driverStrLen) memcpy(header.driver, driverInfoStruct.deviceName.constData(), driverStrLen); header.driver[driverStrLen] = '\0'; @@ -1413,7 +1618,7 @@ void QRhiGles2::setPipelineCacheData(const QByteArray &data) const size_t headerSize = sizeof(QGles2PipelineCacheDataHeader); if (data.size() < qsizetype(headerSize)) { - qWarning("setPipelineCacheData: Invalid blob size (header incomplete)"); + qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob size (header incomplete)"); return; } const size_t dataOffset = headerSize; @@ -1422,27 +1627,27 @@ void QRhiGles2::setPipelineCacheData(const QByteArray &data) const quint32 rhiId = pipelineCacheRhiId(); if (header.rhiId != rhiId) { - qWarning("setPipelineCacheData: The data is for a different QRhi version or backend (%u, %u)", - rhiId, header.rhiId); + qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: The data is for a different QRhi version or backend (%u, %u)", + rhiId, header.rhiId); return; } const quint32 arch = quint32(sizeof(void*)); if (header.arch != arch) { - qWarning("setPipelineCacheData: Architecture does not match (%u, %u)", - arch, header.arch); + qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Architecture does not match (%u, %u)", + arch, header.arch); return; } if (header.programBinaryCount == 0) return; - const size_t driverStrLen = qMin(sizeof(header.driver) - 1, size_t(driverInfoStruct.deviceName.count())); + const size_t driverStrLen = qMin(sizeof(header.driver) - 1, size_t(driverInfoStruct.deviceName.size())); if (strncmp(header.driver, driverInfoStruct.deviceName.constData(), driverStrLen)) { - qWarning("setPipelineCacheData: OpenGL vendor/renderer/version does not match"); + qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: OpenGL vendor/renderer/version does not match"); return; } if (data.size() < qsizetype(dataOffset + header.dataSize)) { - qWarning("setPipelineCacheData: Invalid blob size (data incomplete)"); + qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob size (data incomplete)"); return; } @@ -1470,7 +1675,7 @@ void QRhiGles2::setPipelineCacheData(const QByteArray &data) m_pipelineCache.insert(key, { format, data }); } - qCDebug(QRHI_LOG_INFO, "Seeded pipeline cache with %d program binaries", int(m_pipelineCache.count())); + qCDebug(QRHI_LOG_INFO, "Seeded pipeline cache with %d program binaries", int(m_pipelineCache.size())); } QRhiRenderBuffer *QRhiGles2::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, @@ -1552,8 +1757,8 @@ void QRhiGles2::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind QGles2ShaderResourceBindings *srbD = QRHI_RES(QGles2ShaderResourceBindings, srb); if (cbD->passNeedsResourceTracking) { QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]); - for (int i = 0, ie = srbD->m_bindings.count(); i != ie; ++i) { - const QRhiShaderResourceBinding::Data *b = srbD->m_bindings.at(i).data(); + for (int i = 0, ie = srbD->m_bindings.size(); i != ie; ++i) { + const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->m_bindings.at(i)); switch (b->type) { case QRhiShaderResourceBinding::UniformBuffer: // no BufUniformRead / AccessUniform because no real uniform buffers are used @@ -1830,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) @@ -1862,8 +2071,14 @@ void QRhiGles2::beginExternal(QRhiCommandBuffer *cb) cbD->resetCommands(); - if (vao) + if (vao) { f->glBindVertexArray(0); + } else { + f->glBindBuffer(GL_ARRAY_BUFFER, 0); + f->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + if (caps.compute) + f->glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + } } void QRhiGles2::endExternal(QRhiCommandBuffer *cb) @@ -1887,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); @@ -1900,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; } @@ -1910,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; @@ -1942,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; @@ -1954,13 +2198,29 @@ 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; executeCommandBuffer(&ofr.cbWrapper); + // Just as endFrame() does a flush when skipping the swapBuffers(), do it + // here as well. This has the added benefit of playing nice when rendering + // to a texture from a context and then consuming that texture from + // 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; } @@ -1982,6 +2242,12 @@ QRhi::FrameOpResult QRhiGles2::finish() executeCommandBuffer(¤tSwapChain->cb); currentSwapChain->cb.resetCommands(); } + // Do an actual glFinish(). May seem superfluous, but this is what + // matches most other backends e.g. Vulkan/Metal that do a heavyweight + // wait-for-idle blocking in their finish(). More importantly, this + // allows clients simply call finish() in threaded or shared context + // situations where one explicitly needs to do a glFlush or Finish. + f->glFinish(); } return QRhi::FrameOpSuccess; } @@ -2068,37 +2334,64 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb const bool isCompressed = isCompressedFormat(texD->m_format); const bool isCubeMap = texD->m_flags.testFlag(QRhiTexture::CubeMap); const bool is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional); + const bool is1D = texD->m_flags.testFlag(QRhiTexture::OneDimensional); const bool isArray = texD->m_flags.testFlag(QRhiTexture::TextureArray); const GLenum faceTargetBase = isCubeMap ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target; 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; cmd.args.subImage.level = level; cmd.args.subImage.dx = dp.x(); - cmd.args.subImage.dy = dp.y(); + 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; - 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) { @@ -2110,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; @@ -2122,8 +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 = 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; @@ -2139,7 +2432,7 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb cmd.args.compressedSubImage.faceTarget = effectiveTarget; cmd.args.compressedSubImage.level = level; cmd.args.compressedSubImage.dx = dp.x(); - cmd.args.compressedSubImage.dy = dp.y(); + cmd.args.compressedSubImage.dy = is1D && isArray ? layer : dp.y(); cmd.args.compressedSubImage.dz = is3D || isArray ? layer : 0; cmd.args.compressedSubImage.w = size.width(); cmd.args.compressedSubImage.h = size.height(); @@ -2155,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 = 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 = 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); } @@ -2254,9 +2524,9 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate const QRhiResourceUpdateBatchPrivate::TextureOp &u(ud->textureOps[opIdx]); if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) { QGles2Texture *texD = QRHI_RES(QGles2Texture, u.dst); - for (int layer = 0, maxLayer = u.subresDesc.count(); layer < maxLayer; ++layer) { + for (int layer = 0, maxLayer = u.subresDesc.size(); layer < maxLayer; ++layer) { for (int level = 0; level < QRhi::MAX_MIP_LEVELS; ++level) { - for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level])) + for (const QRhiTextureSubresourceUploadDescription &subresDesc : std::as_const(u.subresDesc[layer][level])) enqueueSubresUpload(texD, cbD, layer, level, subresDesc); } } @@ -2285,6 +2555,8 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate const bool srcHasZ = srcD->m_flags.testFlag(QRhiTexture::ThreeDimensional) || srcD->m_flags.testFlag(QRhiTexture::TextureArray); const bool dstHasZ = dstD->m_flags.testFlag(QRhiTexture::ThreeDimensional) || dstD->m_flags.testFlag(QRhiTexture::TextureArray); + const bool dstIs1dArray = dstD->m_flags.testFlag(QRhiTexture::OneDimensional) + && dstD->m_flags.testFlag(QRhiTexture::TextureArray); cmd.args.copyTex.srcTarget = srcD->target; cmd.args.copyTex.srcFaceTarget = srcFaceTargetBase + (srcHasZ ? 0u : uint(u.desc.sourceLayer())); @@ -2299,7 +2571,7 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate cmd.args.copyTex.dstTexture = dstD->texture; cmd.args.copyTex.dstLevel = u.desc.destinationLevel(); cmd.args.copyTex.dstX = dp.x(); - cmd.args.copyTex.dstY = dp.y(); + cmd.args.copyTex.dstY = dstIs1dArray ? u.desc.destinationLayer() : dp.y(); cmd.args.copyTex.dstZ = dstHasZ ? u.desc.destinationLayer() : 0; cmd.args.copyTex.w = copySize.width(); @@ -2361,8 +2633,7 @@ static inline GLenum toGlTopology(QRhiGraphicsPipeline::Topology t) case QRhiGraphicsPipeline::Patches: return GL_PATCHES; default: - Q_UNREACHABLE(); - return GL_TRIANGLES; + Q_UNREACHABLE_RETURN(GL_TRIANGLES); } } @@ -2374,8 +2645,7 @@ static inline GLenum toGlCullMode(QRhiGraphicsPipeline::CullMode c) case QRhiGraphicsPipeline::Back: return GL_BACK; default: - Q_UNREACHABLE(); - return GL_BACK; + Q_UNREACHABLE_RETURN(GL_BACK); } } @@ -2387,8 +2657,7 @@ static inline GLenum toGlFrontFace(QRhiGraphicsPipeline::FrontFace f) case QRhiGraphicsPipeline::CW: return GL_CW; default: - Q_UNREACHABLE(); - return GL_CCW; + Q_UNREACHABLE_RETURN(GL_CCW); } } @@ -2432,8 +2701,7 @@ static inline GLenum toGlBlendFactor(QRhiGraphicsPipeline::BlendFactor f) qWarning("Unsupported blend factor %d", f); return GL_ZERO; default: - Q_UNREACHABLE(); - return GL_ZERO; + Q_UNREACHABLE_RETURN(GL_ZERO); } } @@ -2451,8 +2719,7 @@ static inline GLenum toGlBlendOp(QRhiGraphicsPipeline::BlendOp op) case QRhiGraphicsPipeline::Max: return GL_MAX; default: - Q_UNREACHABLE(); - return GL_FUNC_ADD; + Q_UNREACHABLE_RETURN(GL_FUNC_ADD); } } @@ -2476,8 +2743,7 @@ static inline GLenum toGlCompareOp(QRhiGraphicsPipeline::CompareOp op) case QRhiGraphicsPipeline::Always: return GL_ALWAYS; default: - Q_UNREACHABLE(); - return GL_ALWAYS; + Q_UNREACHABLE_RETURN(GL_ALWAYS); } } @@ -2501,8 +2767,19 @@ static inline GLenum toGlStencilOp(QRhiGraphicsPipeline::StencilOp op) case QRhiGraphicsPipeline::DecrementAndWrap: return GL_DECR_WRAP; default: - Q_UNREACHABLE(); - return GL_KEEP; + Q_UNREACHABLE_RETURN(GL_KEEP); + } +} + +static inline GLenum toGlPolygonMode(QRhiGraphicsPipeline::PolygonMode mode) +{ + switch (mode) { + case QRhiGraphicsPipeline::PolygonMode::Fill: + return GL_FILL; + case QRhiGraphicsPipeline::PolygonMode::Line: + return GL_LINE; + default: + Q_UNREACHABLE_RETURN(GL_FILL); } } @@ -2520,8 +2797,7 @@ static inline GLenum toGlMinFilter(QRhiSampler::Filter f, QRhiSampler::Filter m) else return m == QRhiSampler::Nearest ? GL_LINEAR_MIPMAP_NEAREST : GL_LINEAR_MIPMAP_LINEAR; default: - Q_UNREACHABLE(); - return GL_LINEAR; + Q_UNREACHABLE_RETURN(GL_LINEAR); } } @@ -2533,8 +2809,7 @@ static inline GLenum toGlMagFilter(QRhiSampler::Filter f) case QRhiSampler::Linear: return GL_LINEAR; default: - Q_UNREACHABLE(); - return GL_LINEAR; + Q_UNREACHABLE_RETURN(GL_LINEAR); } } @@ -2548,8 +2823,7 @@ static inline GLenum toGlWrapMode(QRhiSampler::AddressMode m) case QRhiSampler::Mirror: return GL_MIRRORED_REPEAT; default: - Q_UNREACHABLE(); - return GL_CLAMP_TO_EDGE; + Q_UNREACHABLE_RETURN(GL_CLAMP_TO_EDGE); } } @@ -2573,8 +2847,7 @@ static inline GLenum toGlTextureCompareFunc(QRhiSampler::CompareOp op) case QRhiSampler::Always: return GL_ALWAYS; default: - Q_UNREACHABLE(); - return GL_NEVER; + Q_UNREACHABLE_RETURN(GL_NEVER); } } @@ -2703,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); @@ -2719,8 +2994,18 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) f->glVertexAttribDivisor(GLuint(i), 0); state.instancedAttributesUsed = false; } +#ifdef Q_OS_WASM + for (int i = 0; i < CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT; ++i) { + if (state.enabledAttribArrays[i]) { + f->glDisableVertexAttribArray(GLuint(i)); + state.enabledAttribArrays[i] = false; + } + } +#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) @@ -2849,6 +3134,54 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) type = GL_INT; size = 1; break; + case QRhiVertexInputAttribute::Half4: + type = GL_HALF_FLOAT; + size = 4; + break; + case QRhiVertexInputAttribute::Half3: + type = GL_HALF_FLOAT; + size = 3; + break; + case QRhiVertexInputAttribute::Half2: + type = GL_HALF_FLOAT; + size = 2; + break; + case QRhiVertexInputAttribute::Half: + 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; } @@ -2875,7 +3208,7 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) f->glEnableVertexAttribArray(GLuint(locationIdx)); } if (inputBinding->classification() == QRhiVertexInputBinding::PerInstance && caps.instancing) { - f->glVertexAttribDivisor(GLuint(locationIdx), GLuint(inputBinding->instanceStepRate())); + f->glVertexAttribDivisor(GLuint(locationIdx), inputBinding->instanceStepRate()); if (Q_LIKELY(locationIdx < CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT)) state.nonzeroAttribDivisor[locationIdx] = true; else @@ -2972,24 +3305,32 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) cmd.args.bindShaderResources.dynamicOffsetCount); break; case QGles2CommandBuffer::Command::BindFramebuffer: + { + QVarLengthArray<GLenum, 8> bufs; if (cmd.args.bindFramebuffer.fbo) { f->glBindFramebuffer(GL_FRAMEBUFFER, cmd.args.bindFramebuffer.fbo); + const int colorAttCount = cmd.args.bindFramebuffer.colorAttCount; + bufs.append(colorAttCount > 0 ? GL_COLOR_ATTACHMENT0 : GL_NONE); if (caps.maxDrawBuffers > 1) { - const int colorAttCount = cmd.args.bindFramebuffer.colorAttCount; - QVarLengthArray<GLenum, 8> bufs; - for (int i = 0; i < colorAttCount; ++i) + for (int i = 1; i < colorAttCount; ++i) bufs.append(GL_COLOR_ATTACHMENT0 + uint(i)); - f->glDrawBuffers(colorAttCount, bufs.constData()); } } else { f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject()); + if (cmd.args.bindFramebuffer.stereo && cmd.args.bindFramebuffer.stereoTarget == QRhiSwapChain::RightBuffer) + bufs.append(GL_BACK_RIGHT); + else + bufs.append(caps.gles ? GL_BACK : GL_BACK_LEFT); } - if (caps.srgbCapableDefaultFramebuffer) { + if (caps.hasDrawBuffersFunc) + f->glDrawBuffers(bufs.count(), bufs.constData()); + if (caps.srgbWriteControl) { if (cmd.args.bindFramebuffer.srgb) f->glEnable(GL_FRAMEBUFFER_SRGB); else f->glDisable(GL_FRAMEBUFFER_SRGB); } + } break; case QGles2CommandBuffer::Command::Clear: f->glDisable(GL_SCISSOR_TEST); @@ -3001,8 +3342,10 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) f->glDepthMask(GL_TRUE); f->glClearDepthf(cmd.args.clear.d); } - if (cmd.args.clear.mask & GL_STENCIL_BUFFER_BIT) + if (cmd.args.clear.mask & GL_STENCIL_BUFFER_BIT) { + f->glStencilMask(0xFF); f->glClearStencil(GLint(cmd.args.clear.s)); + } f->glClear(cmd.args.clear.mask); cbD->graphicsPassState.reset(); // altered depth/color write, invalidate in order to avoid confusing the state tracking break; @@ -3013,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) { @@ -3043,9 +3386,15 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) GLuint fbo; f->glGenFramebuffers(1, &fbo); f->glBindFramebuffer(GL_FRAMEBUFFER, fbo); - if (cmd.args.copyTex.srcTarget == GL_TEXTURE_3D || cmd.args.copyTex.srcTarget == GL_TEXTURE_2D_ARRAY) { + if (cmd.args.copyTex.srcTarget == GL_TEXTURE_3D + || cmd.args.copyTex.srcTarget == GL_TEXTURE_2D_ARRAY + || cmd.args.copyTex.srcTarget == GL_TEXTURE_1D_ARRAY) { f->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.copyTex.srcTexture, cmd.args.copyTex.srcLevel, cmd.args.copyTex.srcZ); + } else if (cmd.args.copyTex.srcTarget == GL_TEXTURE_1D) { + glFramebufferTexture1D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + cmd.args.copyTex.srcTarget, cmd.args.copyTex.srcTexture, + cmd.args.copyTex.srcLevel); } else { f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.copyTex.srcFaceTarget, cmd.args.copyTex.srcTexture, cmd.args.copyTex.srcLevel); @@ -3056,6 +3405,10 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) cmd.args.copyTex.dstX, cmd.args.copyTex.dstY, cmd.args.copyTex.dstZ, cmd.args.copyTex.srcX, cmd.args.copyTex.srcY, cmd.args.copyTex.w, cmd.args.copyTex.h); + } else if (cmd.args.copyTex.dstTarget == GL_TEXTURE_1D) { + glCopyTexSubImage1D(cmd.args.copyTex.dstTarget, cmd.args.copyTex.dstLevel, + cmd.args.copyTex.dstX, cmd.args.copyTex.srcX, + cmd.args.copyTex.srcY, cmd.args.copyTex.w); } else { f->glCopyTexSubImage2D(cmd.args.copyTex.dstFaceTarget, cmd.args.copyTex.dstLevel, cmd.args.copyTex.dstX, cmd.args.copyTex.dstY, @@ -3082,6 +3435,9 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) if (cmd.args.readPixels.slice3D >= 0) { f->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, mipLevel, cmd.args.readPixels.slice3D); + } else if (cmd.args.readPixels.readTarget == GL_TEXTURE_1D) { + glFramebufferTexture1D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + cmd.args.readPixels.readTarget, tex, mipLevel); } else { f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.readPixels.readTarget, tex, mipLevel); @@ -3123,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()); @@ -3161,6 +3525,11 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) cmd.args.subImage.w, cmd.args.subImage.h, 1, cmd.args.subImage.glformat, cmd.args.subImage.gltype, cmd.args.subImage.data); + } else if (cmd.args.subImage.target == GL_TEXTURE_1D) { + glTexSubImage1D(cmd.args.subImage.target, cmd.args.subImage.level, + cmd.args.subImage.dx, cmd.args.subImage.w, + cmd.args.subImage.glformat, cmd.args.subImage.gltype, + cmd.args.subImage.data); } else { f->glTexSubImage2D(cmd.args.subImage.faceTarget, cmd.args.subImage.level, cmd.args.subImage.dx, cmd.args.subImage.dy, @@ -3180,6 +3549,11 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) cmd.args.compressedImage.glintformat, cmd.args.compressedImage.w, cmd.args.compressedImage.h, cmd.args.compressedImage.depth, 0, cmd.args.compressedImage.size, cmd.args.compressedImage.data); + } else if (cmd.args.compressedImage.target == GL_TEXTURE_1D) { + glCompressedTexImage1D( + cmd.args.compressedImage.target, cmd.args.compressedImage.level, + cmd.args.compressedImage.glintformat, cmd.args.compressedImage.w, 0, + cmd.args.compressedImage.size, cmd.args.compressedImage.data); } else { f->glCompressedTexImage2D(cmd.args.compressedImage.faceTarget, cmd.args.compressedImage.level, cmd.args.compressedImage.glintformat, @@ -3195,6 +3569,12 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) cmd.args.compressedSubImage.w, cmd.args.compressedSubImage.h, 1, cmd.args.compressedSubImage.glintformat, cmd.args.compressedSubImage.size, cmd.args.compressedSubImage.data); + } else if (cmd.args.compressedImage.target == GL_TEXTURE_1D) { + glCompressedTexSubImage1D( + cmd.args.compressedSubImage.target, cmd.args.compressedSubImage.level, + cmd.args.compressedSubImage.dx, cmd.args.compressedSubImage.w, + cmd.args.compressedSubImage.glintformat, cmd.args.compressedSubImage.size, + cmd.args.compressedSubImage.data); } else { f->glCompressedTexSubImage2D(cmd.args.compressedSubImage.faceTarget, cmd.args.compressedSubImage.level, cmd.args.compressedSubImage.dx, cmd.args.compressedSubImage.dy, @@ -3205,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); } @@ -3270,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; } @@ -3318,6 +3810,12 @@ void QRhiGles2::executeBindGraphicsPipeline(QGles2CommandBuffer *cbD, QGles2Grap f->glFrontFace(frontFace); } + const GLenum polygonMode = toGlPolygonMode(psD->m_polygonMode); + if (glPolygonMode && (forceUpdate || polygonMode != state.polygonMode)) { + state.polygonMode = polygonMode; + glPolygonMode(GL_FRONT_AND_BACK, polygonMode); + } + if (!psD->m_targetBlends.isEmpty()) { // We do not have MRT support here, meaning all targets use the blend // params from the first one. This is technically incorrect, even if @@ -3469,9 +3967,10 @@ void QRhiGles2::executeBindGraphicsPipeline(QGles2CommandBuffer *cbD, QGles2Grap f->glUseProgram(psD->program); } -static inline void qrhi_std140_to_packed(float *dst, int vecSize, int elemCount, const void *src) +template <typename T> +static inline void qrhi_std140_to_packed(T *dst, int vecSize, int elemCount, const void *src) { - const float *p = reinterpret_cast<const float *>(src); + const T *p = reinterpret_cast<const T *>(src); for (int i = 0; i < elemCount; ++i) { for (int j = 0; j < vecSize; ++j) dst[vecSize * i + j] = *p++; @@ -3540,7 +4039,11 @@ void QRhiGles2::bindShaderResources(QGles2CommandBuffer *cbD, QGles2ShaderResourceBindings *srbD = QRHI_RES(QGles2ShaderResourceBindings, srb); int texUnit = 1; // start from unit 1, keep 0 for resource mgmt stuff to avoid clashes bool activeTexUnitAltered = false; - QVarLengthArray<float, 256> packedFloatArray; + union data32_t { + float f; + qint32 i; + }; + QVarLengthArray<data32_t, 256> packedArray; QGles2UniformDescriptionVector &uniforms(maybeGraphicsPs ? QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->uniforms : QRHI_RES(QGles2ComputePipeline, maybeComputePs)->uniforms); QGles2UniformState *uniformState = maybeGraphicsPs ? QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->uniformState @@ -3557,8 +4060,8 @@ void QRhiGles2::bindShaderResources(QGles2CommandBuffer *cbD, }; QVarLengthArray<SeparateSampler, 4> separateSamplerBindings; - for (int i = 0, ie = srbD->m_bindings.count(); i != ie; ++i) { - const QRhiShaderResourceBinding::Data *b = srbD->m_bindings.at(i).data(); + for (int i = 0, ie = srbD->m_bindings.size(); i != ie; ++i) { + const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->m_bindings.at(i)); switch (b->type) { case QRhiShaderResourceBinding::UniformBuffer: @@ -3572,7 +4075,7 @@ void QRhiGles2::bindShaderResources(QGles2CommandBuffer *cbD, } QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, b->u.ubuf.buf); const char *bufView = bufD->data.constData() + viewOffset; - for (const QGles2UniformDescription &uniform : qAsConst(uniforms)) { + for (const QGles2UniformDescription &uniform : std::as_const(uniforms)) { if (uniform.binding == b->binding) { // in a uniform buffer everything is at least 4 byte aligned // so this should not cause unaligned reads @@ -3584,11 +4087,16 @@ void QRhiGles2::bindShaderResources(QGles2CommandBuffer *cbD, && uniform.type != QShaderDescription::Vec2 && uniform.type != QShaderDescription::Vec3 && uniform.type != QShaderDescription::Vec4 + && uniform.type != QShaderDescription::Int + && uniform.type != QShaderDescription::Int2 + && uniform.type != QShaderDescription::Int3 + && uniform.type != QShaderDescription::Int4 && uniform.type != QShaderDescription::Mat3 && uniform.type != QShaderDescription::Mat4) { qWarning("Uniform with buffer binding %d, buffer offset %d, type %d is an array, " - "but arrays are only supported for float, vec2, vec3, vec4, mat3 and mat4. " + "but arrays are only supported for float, vec2, vec3, vec4, int, " + "ivec2, ivec3, ivec4, mat3 and mat4. " "Only the first element will be set.", uniform.binding, uniform.offset, uniform.type); } @@ -3618,9 +4126,9 @@ void QRhiGles2::bindShaderResources(QGles2CommandBuffer *cbD, } } else { // input is 16 bytes per element as per std140, have to convert to packed - packedFloatArray.resize(elemCount); - qrhi_std140_to_packed(packedFloatArray.data(), 1, elemCount, src); - f->glUniform1fv(uniform.glslLocation, elemCount, packedFloatArray.constData()); + packedArray.resize(elemCount); + qrhi_std140_to_packed(&packedArray.data()->f, 1, elemCount, src); + f->glUniform1fv(uniform.glslLocation, elemCount, &packedArray.constData()->f); } } break; @@ -3644,9 +4152,9 @@ void QRhiGles2::bindShaderResources(QGles2CommandBuffer *cbD, f->glUniform2fv(uniform.glslLocation, 1, v); } } else { - packedFloatArray.resize(elemCount * 2); - qrhi_std140_to_packed(packedFloatArray.data(), 2, elemCount, src); - f->glUniform2fv(uniform.glslLocation, elemCount, packedFloatArray.constData()); + packedArray.resize(elemCount * 2); + qrhi_std140_to_packed(&packedArray.data()->f, 2, elemCount, src); + f->glUniform2fv(uniform.glslLocation, elemCount, &packedArray.constData()->f); } } break; @@ -3672,9 +4180,9 @@ void QRhiGles2::bindShaderResources(QGles2CommandBuffer *cbD, f->glUniform3fv(uniform.glslLocation, 1, v); } } else { - packedFloatArray.resize(elemCount * 3); - qrhi_std140_to_packed(packedFloatArray.data(), 3, elemCount, src); - f->glUniform3fv(uniform.glslLocation, elemCount, packedFloatArray.constData()); + packedArray.resize(elemCount * 3); + qrhi_std140_to_packed(&packedArray.data()->f, 3, elemCount, src); + f->glUniform3fv(uniform.glslLocation, elemCount, &packedArray.constData()->f); } } break; @@ -3702,7 +4210,7 @@ void QRhiGles2::bindShaderResources(QGles2CommandBuffer *cbD, f->glUniform4fv(uniform.glslLocation, 1, v); } } else { - f->glUniform4fv(uniform.glslLocation, qMax(1, uniform.arrayDim), reinterpret_cast<const float *>(src)); + f->glUniform4fv(uniform.glslLocation, elemCount, reinterpret_cast<const float *>(src)); } } break; @@ -3721,9 +4229,9 @@ void QRhiGles2::bindShaderResources(QGles2CommandBuffer *cbD, memcpy(mat + 6, srcMat + 8, 3 * sizeof(float)); f->glUniformMatrix3fv(uniform.glslLocation, 1, GL_FALSE, mat); } else { - packedFloatArray.resize(elemCount * 9); - qrhi_std140_to_packed(packedFloatArray.data(), 3, elemCount * 3, src); - f->glUniformMatrix3fv(uniform.glslLocation, elemCount, GL_FALSE, packedFloatArray.constData()); + packedArray.resize(elemCount * 9); + qrhi_std140_to_packed(&packedArray.data()->f, 3, elemCount * 3, src); + f->glUniformMatrix3fv(uniform.glslLocation, elemCount, GL_FALSE, &packedArray.constData()->f); } } break; @@ -3731,16 +4239,43 @@ void QRhiGles2::bindShaderResources(QGles2CommandBuffer *cbD, f->glUniformMatrix4fv(uniform.glslLocation, qMax(1, uniform.arrayDim), GL_FALSE, reinterpret_cast<const float *>(src)); break; case QShaderDescription::Int: - f->glUniform1i(uniform.glslLocation, *reinterpret_cast<const qint32 *>(src)); + { + const int elemCount = uniform.arrayDim; + if (elemCount < 1) { + f->glUniform1i(uniform.glslLocation, *reinterpret_cast<const qint32 *>(src)); + } else { + packedArray.resize(elemCount); + qrhi_std140_to_packed(&packedArray.data()->i, 1, elemCount, src); + f->glUniform1iv(uniform.glslLocation, elemCount, &packedArray.constData()->i); + } + } break; case QShaderDescription::Int2: - f->glUniform2iv(uniform.glslLocation, 1, reinterpret_cast<const qint32 *>(src)); + { + const int elemCount = uniform.arrayDim; + if (elemCount < 1) { + f->glUniform2iv(uniform.glslLocation, 1, reinterpret_cast<const qint32 *>(src)); + } else { + packedArray.resize(elemCount * 2); + qrhi_std140_to_packed(&packedArray.data()->i, 2, elemCount, src); + f->glUniform2iv(uniform.glslLocation, elemCount, &packedArray.constData()->i); + } + } break; case QShaderDescription::Int3: - f->glUniform3iv(uniform.glslLocation, 1, reinterpret_cast<const qint32 *>(src)); + { + const int elemCount = uniform.arrayDim; + if (elemCount < 1) { + f->glUniform3iv(uniform.glslLocation, 1, reinterpret_cast<const qint32 *>(src)); + } else { + packedArray.resize(elemCount * 3); + qrhi_std140_to_packed(&packedArray.data()->i, 3, elemCount, src); + f->glUniform3iv(uniform.glslLocation, elemCount, &packedArray.constData()->i); + } + } break; case QShaderDescription::Int4: - f->glUniform4iv(uniform.glslLocation, 1, reinterpret_cast<const qint32 *>(src)); + f->glUniform4iv(uniform.glslLocation, qMax(1, uniform.arrayDim), reinterpret_cast<const qint32 *>(src)); break; case QShaderDescription::Uint: f->glUniform1ui(uniform.glslLocation, *reinterpret_cast<const quint32 *>(src)); @@ -3902,14 +4437,17 @@ QGles2RenderTargetData *QRhiGles2::enqueueBindFramebuffer(QRhiRenderTarget *rt, static const bool doClearColorBuffer = qEnvironmentVariableIntValue("QT_GL_NO_CLEAR_COLOR_BUFFER") == 0; switch (rt->resourceType()) { - case QRhiResource::RenderTarget: - rtD = &QRHI_RES(QGles2ReferenceRenderTarget, rt)->d; + case QRhiResource::SwapChainRenderTarget: + rtD = &QRHI_RES(QGles2SwapChainRenderTarget, rt)->d; if (wantsColorClear) *wantsColorClear = doClearBuffers && doClearColorBuffer; if (wantsDsClear) *wantsDsClear = doClearBuffers; fbCmd.args.bindFramebuffer.fbo = 0; fbCmd.args.bindFramebuffer.colorAttCount = 1; + fbCmd.args.bindFramebuffer.stereo = rtD->stereoTarget.has_value(); + if (fbCmd.args.bindFramebuffer.stereo) + fbCmd.args.bindFramebuffer.stereoTarget = rtD->stereoTarget.value(); break; case QRhiResource::TextureRenderTarget: { @@ -3921,6 +4459,7 @@ QGles2RenderTargetData *QRhiGles2::enqueueBindFramebuffer(QRhiRenderTarget *rt, *wantsDsClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents); fbCmd.args.bindFramebuffer.fbo = rtTex->framebuffer; fbCmd.args.bindFramebuffer.colorAttCount = rtD->colorAttCount; + fbCmd.args.bindFramebuffer.stereo = false; for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments(); it != itEnd; ++it) @@ -3961,7 +4500,7 @@ QGles2RenderTargetData *QRhiGles2::enqueueBindFramebuffer(QRhiRenderTarget *rt, void QRhiGles2::enqueueBarriersForPass(QGles2CommandBuffer *cbD) { cbD->passResTrackers.append(QRhiPassResourceTracker()); - cbD->currentPassResTrackerIndex = cbD->passResTrackers.count() - 1; + cbD->currentPassResTrackerIndex = cbD->passResTrackers.size() - 1; QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::BarriersForPass; cmd.args.barriersForPass.trackerIndex = cbD->currentPassResTrackerIndex; @@ -4021,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; } } } @@ -4139,9 +4775,9 @@ void QRhiGles2::dispatch(QRhiCommandBuffer *cb, int x, int y, int z) accessAndIsNewFlag = { 0, false }; QGles2ShaderResourceBindings *srbD = QRHI_RES(QGles2ShaderResourceBindings, cbD->currentComputeSrb); - const int bindingCount = srbD->m_bindings.count(); + const int bindingCount = srbD->m_bindings.size(); for (int i = 0; i < bindingCount; ++i) { - const QRhiShaderResourceBinding::Data *b = srbD->m_bindings.at(i).data(); + const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->m_bindings.at(i)); switch (b->type) { case QRhiShaderResourceBinding::ImageLoad: case QRhiShaderResourceBinding::ImageStore: @@ -4209,13 +4845,14 @@ static inline GLenum toGlShaderType(QRhiShaderStage::Type type) return GL_TESS_CONTROL_SHADER; case QRhiShaderStage::TessellationEvaluation: return GL_TESS_EVALUATION_SHADER; + case QRhiShaderStage::Geometry: + return GL_GEOMETRY_SHADER; case QRhiShaderStage::Fragment: return GL_FRAGMENT_SHADER; case QRhiShaderStage::Compute: return GL_COMPUTE_SHADER; default: - Q_UNREACHABLE(); - return GL_VERTEX_SHADER; + Q_UNREACHABLE_RETURN(GL_VERTEX_SHADER); } } @@ -4298,7 +4935,7 @@ bool QRhiGles2::compileShader(GLuint program, const QRhiShaderStage &shaderStage } else { shader = f->glCreateShader(toGlShaderType(shaderStage.type())); const char *srcStr = source.constData(); - const GLint srcLength = source.count(); + const GLint srcLength = source.size(); f->glShaderSource(shader, 1, &srcStr, &srcLength); f->glCompileShader(shader); GLint compiled = 0; @@ -4315,7 +4952,7 @@ bool QRhiGles2::compileShader(GLuint program, const QRhiShaderStage &shaderStage qWarning("Failed to compile shader: %s\nSource was:\n%s", log.constData(), source.constData()); return false; } - if (m_shaderCache.count() >= MAX_SHADER_CACHE_ENTRIES) { + if (m_shaderCache.size() >= MAX_SHADER_CACHE_ENTRIES) { // Use the simplest strategy: too many cached shaders -> drop them all. for (uint shader : m_shaderCache) f->glDeleteShader(shader); // does not actually get released yet when attached to a not-yet-released program @@ -4372,7 +5009,7 @@ void QRhiGles2::registerUniformIfActive(const QShaderDescription::BlockVariable // unnecessary glUniform* calls then. uniform.glslLocation = f->glGetUniformLocation(program, name.constData()); if (uniform.glslLocation >= 0 && !activeUniformLocations->hasSeen(uniform.glslLocation)) { - if (var.arrayDims.count() > 1) { + if (var.arrayDims.size() > 1) { qWarning("Array '%s' has more than one dimension. This is not supported.", var.name.constData()); return; @@ -4401,7 +5038,7 @@ void QRhiGles2::gatherUniforms(GLuint program, registerUniformIfActive(structMember, structPrefix + ".", ub.binding, baseOffset, program, activeUniformLocations, dst); } else { - if (blockMember.arrayDims.count() > 1) { + if (blockMember.arrayDims.size() > 1) { qWarning("Array of struct '%s' has more than one dimension. Only the first " "dimension is used.", blockMember.name.constData()); @@ -4486,13 +5123,18 @@ static inline QShader::Stage toShaderStage(QRhiShaderStage::Type type) switch (type) { case QRhiShaderStage::Vertex: return QShader::VertexStage; + case QRhiShaderStage::TessellationControl: + return QShader::TessellationControlStage; + case QRhiShaderStage::TessellationEvaluation: + return QShader::TessellationEvaluationStage; + case QRhiShaderStage::Geometry: + return QShader::GeometryStage; case QRhiShaderStage::Fragment: return QShader::FragmentStage; case QRhiShaderStage::Compute: return QShader::ComputeStage; default: - Q_UNREACHABLE(); - return QShader::VertexStage; + Q_UNREACHABLE_RETURN(QShader::VertexStage); } } @@ -4615,7 +5257,7 @@ void QRhiGles2::trySaveToPipelineCache(GLuint program, const QByteArray &cacheKe } } -QGles2Buffer::QGles2Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size) +QGles2Buffer::QGles2Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size) : QRhiBuffer(rhi, type, usage, size) { } @@ -4675,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); @@ -4829,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); @@ -4910,13 +5558,15 @@ bool QGles2Texture::prepareCreate(QSize *adjustedSize) if (!rhiD->ensureContext()) return false; - const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize; - const bool isCube = m_flags.testFlag(CubeMap); const bool isArray = m_flags.testFlag(QRhiTexture::TextureArray); const bool is3D = m_flags.testFlag(ThreeDimensional); const bool hasMipMaps = m_flags.testFlag(MipMapped); const bool isCompressed = rhiD->isCompressedFormat(m_format); + const bool is1D = m_flags.testFlag(OneDimensional); + + const QSize size = is1D ? QSize(qMax(1, m_pixelSize.width()), 1) + : (m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize); if (is3D && !rhiD->caps.texture3D) { qWarning("3D textures are not supported"); @@ -4930,12 +5580,23 @@ bool QGles2Texture::prepareCreate(QSize *adjustedSize) qWarning("Texture cannot be both array and 3D"); return false; } - m_depth = qMax(1, m_depth); + if (is1D && !rhiD->caps.texture1D) { + qWarning("1D textures are not supported"); + return false; + } + if (is1D && is3D) { + qWarning("Texture cannot be both 1D and 3D"); + return false; + } + if (is1D && isCube) { + qWarning("Texture cannot be both 1D and cube"); + return false; + } + 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; @@ -4945,9 +5606,12 @@ bool QGles2Texture::prepareCreate(QSize *adjustedSize) return false; } - target = isCube ? GL_TEXTURE_CUBE_MAP - : m_sampleCount > 1 ? GL_TEXTURE_2D_MULTISAMPLE - : (is3D ? GL_TEXTURE_3D : (isArray ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D)); + target = isCube ? GL_TEXTURE_CUBE_MAP + : 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))); + if (m_flags.testFlag(ExternalOES)) target = GL_TEXTURE_EXTERNAL_OES; else if (m_flags.testFlag(TextureRectangleGL)) @@ -4997,11 +5661,23 @@ bool QGles2Texture::create() const bool is3D = m_flags.testFlag(ThreeDimensional); const bool hasMipMaps = m_flags.testFlag(MipMapped); const bool isCompressed = rhiD->isCompressedFormat(m_format); + const bool is1D = m_flags.testFlag(OneDimensional); + if (!isCompressed) { rhiD->f->glBindTexture(target, texture); if (!m_flags.testFlag(UsedWithLoadStore)) { - if (is3D || isArray) { - const int layerCount = is3D ? m_depth : m_arraySize; + if (is1D) { + for (int level = 0; level < mipLevelCount; ++level) { + const QSize mipSize = rhiD->q->sizeForMipLevel(level, size); + if (isArray) + rhiD->f->glTexImage2D(target, level, GLint(glintformat), mipSize.width(), + 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 ? 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); @@ -5023,17 +5699,32 @@ 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 // bindImageTexture may fail. Also, the internal format must be a // sized format here. - if (is3D || isArray) - rhiD->f->glTexStorage3D(target, mipLevelCount, glsizedintformat, size.width(), size.height(), is3D ? m_depth : m_arraySize); + 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 ? 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(), size.height()); + rhiD->f->glTexStorage2D(target, mipLevelCount, glsizedintformat, size.width(), + is1D ? qMax(0, m_arraySize) : size.height()); } specified = true; } else { @@ -5043,6 +5734,9 @@ bool QGles2Texture::create() specified = false; } + if (rhiD->glObjectLabel) + rhiD->glObjectLabel(GL_TEXTURE, texture, -1, m_objectName.constData()); + owns = true; generation += 1; @@ -5089,7 +5783,9 @@ QGles2Sampler::~QGles2Sampler() void QGles2Sampler::destroy() { - // nothing to do here + QRHI_RES_RHI(QRhiGles2); + if (rhiD) + rhiD->unregisterResource(this); } bool QGles2Sampler::create() @@ -5102,6 +5798,8 @@ bool QGles2Sampler::create() d.gltexcomparefunc = toGlTextureCompareFunc(m_compareOp); generation += 1; + QRHI_RES_RHI(QRhiGles2); + rhiD->registerResource(this, false); return true; } @@ -5118,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 @@ -5129,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 @@ -5137,33 +5840,33 @@ QVector<quint32> QGles2RenderPassDescriptor::serializedFormat() const return {}; } -QGles2ReferenceRenderTarget::QGles2ReferenceRenderTarget(QRhiImplementation *rhi) - : QRhiRenderTarget(rhi), +QGles2SwapChainRenderTarget::QGles2SwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain) + : QRhiSwapChainRenderTarget(rhi, swapchain), d(rhi) { } -QGles2ReferenceRenderTarget::~QGles2ReferenceRenderTarget() +QGles2SwapChainRenderTarget::~QGles2SwapChainRenderTarget() { destroy(); } -void QGles2ReferenceRenderTarget::destroy() +void QGles2SwapChainRenderTarget::destroy() { // nothing to do here } -QSize QGles2ReferenceRenderTarget::pixelSize() const +QSize QGles2SwapChainRenderTarget::pixelSize() const { return d.pixelSize; } -float QGles2ReferenceRenderTarget::devicePixelRatio() const +float QGles2SwapChainRenderTarget::devicePixelRatio() const { return d.dpr; } -int QGles2ReferenceRenderTarget::sampleCount() const +int QGles2SwapChainRenderTarget::sampleCount() const { return d.sampleCount; } @@ -5190,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) { @@ -5202,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() @@ -5212,13 +5920,13 @@ bool QGles2TextureRenderTarget::create() if (framebuffer) destroy(); - const bool hasColorAttachments = m_desc.cbeginColorAttachments() != m_desc.cendColorAttachments(); + const bool hasColorAttachments = m_desc.colorAttachmentCount() > 0; Q_ASSERT(hasColorAttachments || m_desc.depthTexture()); Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture()); const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture(); if (hasColorAttachments) { - const int count = m_desc.cendColorAttachments() - m_desc.cbeginColorAttachments(); + const int count = int(m_desc.colorAttachmentCount()); if (count > rhiD->caps.maxDrawBuffers) { qWarning("QGles2TextureRenderTarget: Too many color attachments (%d, max is %d)", count, rhiD->caps.maxDrawBuffers); @@ -5235,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); @@ -5245,16 +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); @@ -5275,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(); @@ -5288,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; @@ -5309,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); @@ -5317,6 +6165,9 @@ bool QGles2TextureRenderTarget::create() QSize QGles2TextureRenderTarget::pixelSize() const { + if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QGles2Texture, QGles2RenderBuffer>(m_desc, d.currentResIdList)) + const_cast<QGles2TextureRenderTarget *>(this)->create(); + return d.pixelSize; } @@ -5342,7 +6193,9 @@ QGles2ShaderResourceBindings::~QGles2ShaderResourceBindings() void QGles2ShaderResourceBindings::destroy() { - // nothing to do here + QRHI_RES_RHI(QRhiGles2); + if (rhiD) + rhiD->unregisterResource(this); } bool QGles2ShaderResourceBindings::create() @@ -5352,8 +6205,8 @@ bool QGles2ShaderResourceBindings::create() return false; hasDynamicOffset = false; - for (int i = 0, ie = m_bindings.count(); i != ie; ++i) { - const QRhiShaderResourceBinding::Data *b = m_bindings.at(i).data(); + for (int i = 0, ie = m_bindings.size(); i != ie; ++i) { + const QRhiShaderResourceBinding::Data *b = QRhiImplementation::shaderResourceBindingData(m_bindings.at(i)); if (b->type == QRhiShaderResourceBinding::UniformBuffer) { if (b->u.ubuf.hasDynamicOffset) { hasDynamicOffset = true; @@ -5365,6 +6218,7 @@ bool QGles2ShaderResourceBindings::create() rhiD->updateLayoutDesc(this); generation += 1; + rhiD->registerResource(this, false); return true; } @@ -5411,6 +6265,7 @@ static inline bool isGraphicsStage(const QRhiShaderStage &shaderStage) return t == QRhiShaderStage::Vertex || t == QRhiShaderStage::TessellationControl || t == QRhiShaderStage::TessellationEvaluation + || t == QRhiShaderStage::Geometry || t == QRhiShaderStage::Fragment; } @@ -5424,6 +6279,7 @@ bool QGles2GraphicsPipeline::create() if (!rhiD->ensureContext()) return false; + rhiD->pipelineCreationStart(); if (!rhiD->sanityCheckGraphicsPipeline(this)) return false; @@ -5433,8 +6289,9 @@ bool QGles2GraphicsPipeline::create() enum { VtxIdx = 0, - TEIdx, TCIdx, + TEIdx, + GeomIdx, FragIdx, LastIdx }; @@ -5446,19 +6303,23 @@ bool QGles2GraphicsPipeline::create() return TCIdx; case QRhiShaderStage::TessellationEvaluation: return TEIdx; + case QRhiShaderStage::Geometry: + return GeomIdx; case QRhiShaderStage::Fragment: return FragIdx; default: break; } - Q_UNREACHABLE(); - return VtxIdx; + Q_UNREACHABLE_RETURN(VtxIdx); }; QShaderDescription desc[LastIdx]; QShader::SeparateToCombinedImageSamplerMappingList samplerMappingList[LastIdx]; - for (const QRhiShaderStage &shaderStage : qAsConst(m_shaderStages)) { + bool vertexFragmentOnly = true; + for (const QRhiShaderStage &shaderStage : std::as_const(m_shaderStages)) { if (isGraphicsStage(shaderStage)) { const int idx = descIdxForStage(shaderStage); + if (idx != VtxIdx && idx != FragIdx) + vertexFragmentOnly = false; QShader shader = shaderStage.shader(); QShaderVersion shaderVersion; desc[idx] = shader.description(); @@ -5471,7 +6332,7 @@ bool QGles2GraphicsPipeline::create() QByteArray cacheKey; QRhiGles2::ProgramCacheResult cacheResult = rhiD->tryLoadFromDiskOrPipelineCache(m_shaderStages.constData(), - m_shaderStages.count(), + m_shaderStages.size(), program, desc[VtxIdx].inputVariables(), &cacheKey); @@ -5479,7 +6340,7 @@ bool QGles2GraphicsPipeline::create() return false; if (cacheResult == QRhiGles2::ProgramCacheMiss) { - for (const QRhiShaderStage &shaderStage : qAsConst(m_shaderStages)) { + for (const QRhiShaderStage &shaderStage : std::as_const(m_shaderStages)) { if (isGraphicsStage(shaderStage)) { if (!rhiD->compileShader(program, shaderStage, nullptr)) return false; @@ -5490,7 +6351,8 @@ bool QGles2GraphicsPipeline::create() for (const QShaderDescription::InOutVariable &inVar : desc[VtxIdx].inputVariables()) rhiD->f->glBindAttribLocation(program, GLuint(inVar.location), inVar.name); - rhiD->sanityCheckVertexFragmentInterface(desc[VtxIdx], desc[FragIdx]); + if (vertexFragmentOnly) + rhiD->sanityCheckVertexFragmentInterface(desc[VtxIdx], desc[FragIdx]); if (!rhiD->linkProgram(program)) return false; @@ -5518,7 +6380,7 @@ bool QGles2GraphicsPipeline::create() // present in both shaders. QDuplicateTracker<int, 256> activeUniformLocations; - for (const QRhiShaderStage &shaderStage : qAsConst(m_shaderStages)) { + for (const QRhiShaderStage &shaderStage : std::as_const(m_shaderStages)) { if (isGraphicsStage(shaderStage)) { const int idx = descIdxForStage(shaderStage); for (const QShaderDescription::UniformBlock &ub : desc[idx].uniformBlocks()) @@ -5541,6 +6403,10 @@ 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); return true; @@ -5587,6 +6453,8 @@ bool QGles2ComputePipeline::create() if (!rhiD->ensureContext()) return false; + rhiD->pipelineCreationStart(); + const QShaderDescription csDesc = m_shaderStage.shader().description(); QShader::SeparateToCombinedImageSamplerMappingList csSamplerMappingList; QShaderVersion shaderVersion; @@ -5642,6 +6510,7 @@ bool QGles2ComputePipeline::create() currentSrb = nullptr; currentSrbGeneration = 0; + rhiD->pipelineCreationEnd(); generation += 1; rhiD->registerResource(this); return true; @@ -5665,7 +6534,9 @@ void QGles2CommandBuffer::destroy() QGles2SwapChain::QGles2SwapChain(QRhiImplementation *rhi) : QRhiSwapChain(rhi), - rt(rhi), + rt(rhi, this), + rtLeft(rhi, this), + rtRight(rhi, this), cb(rhi) { } @@ -5692,10 +6563,25 @@ QRhiRenderTarget *QGles2SwapChain::currentFrameRenderTarget() return &rt; } +QRhiRenderTarget *QGles2SwapChain::currentFrameRenderTarget(StereoTargetBuffer targetBuffer) +{ + if (targetBuffer == LeftBuffer) + return rtLeft.d.isValid() ? &rtLeft : &rt; + else if (targetBuffer == RightBuffer) + return rtRight.d.isValid() ? &rtRight : &rt; + else + Q_UNREACHABLE_RETURN(nullptr); +} + 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) @@ -5705,7 +6591,22 @@ 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) +{ + rt->setRenderPassDescriptor(m_renderPassDesc); // for the public getter in QRhiRenderTarget + rt->d.rp = QRHI_RES(QGles2RenderPassDescriptor, m_renderPassDesc); + rt->d.pixelSize = pixelSize; + rt->d.dpr = float(m_window->devicePixelRatio()); + rt->d.sampleCount = qBound(1, m_sampleCount, 64); + rt->d.colorAttCount = 1; + rt->d.dsAttCount = m_depthStencil ? 1 : 0; + rt->d.srgbUpdateAndBlend = m_flags.testFlag(QRhiSwapChain::sRGB); } bool QGles2SwapChain::createOrResize() @@ -5726,25 +6627,70 @@ bool QGles2SwapChain::createOrResize() m_depthStencil->create(); } - rt.d.rp = QRHI_RES(QGles2RenderPassDescriptor, m_renderPassDesc); - rt.d.pixelSize = pixelSize; - rt.d.dpr = float(m_window->devicePixelRatio()); - rt.d.sampleCount = qBound(1, m_sampleCount, 64); - rt.d.colorAttCount = 1; - rt.d.dsAttCount = m_depthStencil ? 1 : 0; - rt.d.srgbUpdateAndBlend = m_flags.testFlag(QRhiSwapChain::sRGB); + initSwapChainRenderTarget(&rt); + + if (m_window->format().stereo()) { + initSwapChainRenderTarget(&rtLeft); + rtLeft.d.stereoTarget = QRhiSwapChain::LeftBuffer; + initSwapChainRenderTarget(&rtRight); + rtRight.d.stereoTarget = QRhiSwapChain::RightBuffer; + } 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 |