/**************************************************************************** ** ** Copyright (C) 2019 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 #include #include #include #include #include QT_BEGIN_NAMESPACE /* OpenGL backend. Binding vertex attribute locations and decomposing uniform buffers into uniforms are handled transparently to the application via the reflection data (QShaderDescription). Real uniform buffers are never used, regardless of the GLSL version. Textures and buffers feature no special logic, it's all just glTexSubImage2D and glBufferSubData (with "dynamic" buffers set to GL_DYNAMIC_DRAW). The swapchain and the associated renderbuffer for depth-stencil will be dummies since we have no control over the underlying buffers here. While the baseline here is plain GLES 2.0, some modern GL(ES) features like multisample renderbuffers, blits, and compute are used when available. Also functional with core profile contexts. */ /*! \class QRhiGles2InitParams \internal \inmodule QtGui \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. \badcode QOffscreenSurface *fallbackSurface = QRhiGles2InitParams::newFallbackSurface(); QRhiGles2InitParams params; params.fallbackSurface = fallbackSurface; params.window = window; rhi = QRhi::create(QRhi::OpenGLES2, ¶ms); \endcode By default QRhi creates a QOpenGLContext on its own. This approach works well in most cases, included threaded scenarios, where there is a dedicated QRhi for each rendering thread. As there will be a QOpenGLContext for each QRhi, the OpenGL context requirements (a context can only be current on one 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 constructor sets this to QSurfaceFormat::defaultFormat() so applications that use QSurfaceFormat::setDefaultFormat() do not need to set the format again. \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. 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. As a convenience, applications can use newFallbackSurface() which creates and returns a QOffscreenSurface that is compatible with the QOpenGLContext that is going to be created by the QRhi afterwards. Note that the ownership of the returned QOffscreenSurface is transferred to the caller and the QRhi will not destroy it. \note QRhiSwapChain can only target QWindow instances that have their surface type set to QSurface::OpenGLSurface. \note \l 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 an invisible window on some platforms (for example, Windows) and that may trigger unexpected problems in some cases. \section2 Working with existing OpenGL contexts 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. An alternative approach is to create a QOpenGLContext that \l{QOpenGLContext::setShareContext()}{shares resources} with the other engine's context and passing in that context via QRhiGles2NativeHandles. The QRhi does not take ownership of the QOpenGLContext passed in via QRhiGles2NativeHandles. */ /*! \class QRhiGles2NativeHandles \internal \inmodule QtGui \brief Holds the OpenGL context used by the QRhi. */ #ifndef GL_BGRA #define GL_BGRA 0x80E1 #endif #ifndef GL_R8 #define GL_R8 0x8229 #endif #ifndef GL_RG8 #define GL_RG8 0x822B #endif #ifndef GL_RG #define GL_RG 0x8227 #endif #ifndef GL_R16 #define GL_R16 0x822A #endif #ifndef GL_RG16 #define GL_RG16 0x822C #endif #ifndef GL_RED #define GL_RED 0x1903 #endif #ifndef GL_RGBA8 #define GL_RGBA8 0x8058 #endif #ifndef GL_RGBA32F #define GL_RGBA32F 0x8814 #endif #ifndef GL_RGBA16F #define GL_RGBA16F 0x881A #endif #ifndef GL_R16F #define GL_R16F 0x822D #endif #ifndef GL_R32F #define GL_R32F 0x822E #endif #ifndef GL_HALF_FLOAT #define GL_HALF_FLOAT 0x140B #endif #ifndef GL_DEPTH_COMPONENT16 #define GL_DEPTH_COMPONENT16 0x81A5 #endif #ifndef GL_DEPTH_COMPONENT24 #define GL_DEPTH_COMPONENT24 0x81A6 #endif #ifndef GL_DEPTH_COMPONENT32F #define GL_DEPTH_COMPONENT32F 0x8CAC #endif #ifndef GL_STENCIL_INDEX #define GL_STENCIL_INDEX 0x1901 #endif #ifndef GL_STENCIL_INDEX8 #define GL_STENCIL_INDEX8 0x8D48 #endif #ifndef GL_DEPTH24_STENCIL8 #define GL_DEPTH24_STENCIL8 0x88F0 #endif #ifndef GL_DEPTH_STENCIL_ATTACHMENT #define GL_DEPTH_STENCIL_ATTACHMENT 0x821A #endif #ifndef GL_DEPTH_STENCIL #define GL_DEPTH_STENCIL 0x84F9 #endif #ifndef GL_PRIMITIVE_RESTART_FIXED_INDEX #define GL_PRIMITIVE_RESTART_FIXED_INDEX 0x8D69 #endif #ifndef GL_FRAMEBUFFER_SRGB #define GL_FRAMEBUFFER_SRGB 0x8DB9 #endif #ifndef GL_READ_FRAMEBUFFER #define GL_READ_FRAMEBUFFER 0x8CA8 #endif #ifndef GL_DRAW_FRAMEBUFFER #define GL_DRAW_FRAMEBUFFER 0x8CA9 #endif #ifndef GL_MAX_DRAW_BUFFERS #define GL_MAX_DRAW_BUFFERS 0x8824 #endif #ifndef GL_TEXTURE_COMPARE_MODE #define GL_TEXTURE_COMPARE_MODE 0x884C #endif #ifndef GL_COMPARE_REF_TO_TEXTURE #define GL_COMPARE_REF_TO_TEXTURE 0x884E #endif #ifndef GL_TEXTURE_COMPARE_FUNC #define GL_TEXTURE_COMPARE_FUNC 0x884D #endif #ifndef GL_MAX_SAMPLES #define GL_MAX_SAMPLES 0x8D57 #endif #ifndef GL_SHADER_STORAGE_BUFFER #define GL_SHADER_STORAGE_BUFFER 0x90D2 #endif #ifndef GL_READ_ONLY #define GL_READ_ONLY 0x88B8 #endif #ifndef GL_WRITE_ONLY #define GL_WRITE_ONLY 0x88B9 #endif #ifndef GL_READ_WRITE #define GL_READ_WRITE 0x88BA #endif #ifndef GL_COMPUTE_SHADER #define GL_COMPUTE_SHADER 0x91B9 #endif #ifndef GL_ALL_BARRIER_BITS #define GL_ALL_BARRIER_BITS 0xFFFFFFFF #endif #ifndef GL_SHADER_IMAGE_ACCESS_BARRIER_BIT #define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT 0x00000020 #endif #ifndef GL_SHADER_STORAGE_BARRIER_BIT #define GL_SHADER_STORAGE_BARRIER_BIT 0x00002000 #endif #ifndef GL_VERTEX_PROGRAM_POINT_SIZE #define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 #endif #ifndef GL_POINT_SPRITE #define GL_POINT_SPRITE 0x8861 #endif #ifndef GL_MAP_READ_BIT #define GL_MAP_READ_BIT 0x0001 #endif #ifndef GL_MAP_WRITE_BIT #define GL_MAP_WRITE_BIT 0x0002 #endif #ifndef GL_TEXTURE_2D_MULTISAMPLE #define GL_TEXTURE_2D_MULTISAMPLE 0x9100 #endif #ifndef GL_TEXTURE_EXTERNAL_OES #define GL_TEXTURE_EXTERNAL_OES 0x8D65 #endif #ifndef GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS #define GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS 0x90EB #endif #ifndef GL_MAX_COMPUTE_WORK_GROUP_COUNT #define GL_MAX_COMPUTE_WORK_GROUP_COUNT 0x91BE #endif #ifndef GL_MAX_COMPUTE_WORK_GROUP_SIZE #define GL_MAX_COMPUTE_WORK_GROUP_SIZE 0x91BF #endif #ifndef GL_TEXTURE_CUBE_MAP_SEAMLESS #define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F #endif #ifndef GL_CONTEXT_LOST #define GL_CONTEXT_LOST 0x0507 #endif #ifndef GL_PROGRAM_BINARY_LENGTH #define GL_PROGRAM_BINARY_LENGTH 0x8741 #endif #ifndef GL_NUM_PROGRAM_BINARY_FORMATS #define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE #endif #ifndef GL_UNPACK_ROW_LENGTH #define GL_UNPACK_ROW_LENGTH 0x0CF2 #endif #ifndef GL_TEXTURE_3D #define GL_TEXTURE_3D 0x806F #endif #ifndef GL_TEXTURE_WRAP_R #define GL_TEXTURE_WRAP_R 0x8072 #endif /*! Constructs a new QRhiGles2InitParams. \l format is set to QSurfaceFormat::defaultFormat(). */ QRhiGles2InitParams::QRhiGles2InitParams() { format = QSurfaceFormat::defaultFormat(); } /*! \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. \a format is adjusted as appropriate in order to avoid having problems afterwards due to an incompatible context and surface. \note This function must only be called on the gui/main thread. \note It is the application's responsibility to destroy the returned QOffscreenSurface on the gui/main thread once the associated QRhi has been destroyed. The QRhi will not destroy the QOffscreenSurface. */ QOffscreenSurface *QRhiGles2InitParams::newFallbackSurface(const QSurfaceFormat &format) { QSurfaceFormat fmt = adjustedFormat(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. QOpenGLContext tempContext; tempContext.setFormat(fmt); if (tempContext.create()) fmt = tempContext.format(); else qWarning("QRhiGles2: Failed to create temporary context"); QOffscreenSurface *s = new QOffscreenSurface; s->setFormat(fmt); s->create(); return s; } QRhiGles2::QRhiGles2(QRhiGles2InitParams *params, QRhiGles2NativeHandles *importDevice) : ofr(this) { requestedFormat = QRhiGles2InitParams::adjustedFormat(params->format); fallbackSurface = params->fallbackSurface; maybeWindow = params->window; // may be null importedContext = importDevice != nullptr; if (importedContext) { ctx = importDevice->context; if (!ctx) { qWarning("No OpenGL context given, cannot import"); importedContext = false; } } } bool QRhiGles2::ensureContext(QSurface *surface) const { bool nativeWindowGone = false; if (surface && surface->surfaceClass() == QSurface::Window && !surface->surfaceHandle()) { surface = fallbackSurface; nativeWindowGone = true; } if (!surface) surface = fallbackSurface; if (needsMakeCurrent) needsMakeCurrent = false; else if (!nativeWindowGone && QOpenGLContext::currentContext() == ctx && (surface == fallbackSurface || ctx->surface() == surface)) return true; if (!ctx->makeCurrent(surface)) { if (ctx->isValid()) { qWarning("QRhiGles2: Failed to make context current. Expect bad things to happen."); } else { qWarning("QRhiGles2: Context is lost."); contextLost = true; } return false; } return true; } bool QRhiGles2::create(QRhi::Flags flags) { Q_ASSERT(fallbackSurface); rhiFlags = flags; if (!importedContext) { ctx = new QOpenGLContext; ctx->setFormat(requestedFormat); if (QOpenGLContext *shareContext = qt_gl_global_share_context()) { ctx->setShareContext(shareContext); ctx->setScreen(shareContext->screen()); } if (!ctx->create()) { qWarning("QRhiGles2: Failed to create context"); delete ctx; ctx = nullptr; return false; } qCDebug(QRHI_LOG_INFO) << "Created OpenGL context" << ctx->format(); } if (!ensureContext(maybeWindow ? maybeWindow : fallbackSurface)) // see 'window' discussion in QRhiGles2InitParams comments return false; f = static_cast(ctx->extraFunctions()); const char *vendor = reinterpret_cast(f->glGetString(GL_VENDOR)); const char *renderer = reinterpret_cast(f->glGetString(GL_RENDERER)); const char *version = reinterpret_cast(f->glGetString(GL_VERSION)); if (vendor && renderer && version) qCDebug(QRHI_LOG_INFO, "OpenGL VENDOR: %s RENDERER: %s VERSION: %s", vendor, renderer, version); if (vendor) { driverInfoStruct.deviceName += QByteArray(vendor); driverInfoStruct.deviceName += ' '; } if (renderer) { driverInfoStruct.deviceName += QByteArray(renderer); driverInfoStruct.deviceName += ' '; } if (version) driverInfoStruct.deviceName += QByteArray(version); const QSurfaceFormat actualFormat = ctx->format(); caps.ctxMajor = actualFormat.majorVersion(); caps.ctxMinor = actualFormat.minorVersion(); GLint n = 0; f->glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &n); supportedCompressedFormats.resize(n); if (n > 0) f->glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, supportedCompressedFormats.data()); f->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &caps.maxTextureSize); if (caps.ctxMajor >= 3 || actualFormat.renderableType() == QSurfaceFormat::OpenGL) { f->glGetIntegerv(GL_MAX_DRAW_BUFFERS, &caps.maxDrawBuffers); f->glGetIntegerv(GL_MAX_SAMPLES, &caps.maxSamples); caps.maxSamples = qMax(1, caps.maxSamples); } else { caps.maxDrawBuffers = 1; caps.maxSamples = 1; } caps.msaaRenderBuffer = f->hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample) && f->hasOpenGLExtension(QOpenGLExtensions::FramebufferBlit); 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) f->glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX); caps.bgraExternalFormat = f->hasOpenGLExtension(QOpenGLExtensions::BGRATextureFormat); caps.bgraInternalFormat = caps.bgraExternalFormat && caps.gles; caps.r8Format = f->hasOpenGLFeature(QOpenGLFunctions::TextureRGFormats); caps.r16Format = f->hasOpenGLExtension(QOpenGLExtensions::Sized16Formats); caps.floatFormats = caps.ctxMajor >= 3; // 3.0 or ES 3.0 caps.depthTexture = caps.ctxMajor >= 3; // 3.0 or ES 3.0 caps.packedDepthStencil = f->hasOpenGLExtension(QOpenGLExtensions::PackedDepthStencil); #ifdef Q_OS_WASM caps.needsDepthStencilCombinedAttach = true; #else caps.needsDepthStencilCombinedAttach = false; #endif caps.srgbCapableDefaultFramebuffer = f->hasOpenGLExtension(QOpenGLExtensions::SRGBFrameBuffer); caps.coreProfile = actualFormat.profile() == QSurfaceFormat::CoreProfile; if (caps.gles) caps.uniformBuffers = caps.ctxMajor >= 3; // ES 3.0 else caps.uniformBuffers = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 1); // 3.1 caps.elementIndexUint = f->hasOpenGLExtension(QOpenGLExtensions::ElementIndexUint); caps.depth24 = f->hasOpenGLExtension(QOpenGLExtensions::Depth24); caps.rgba8Format = f->hasOpenGLExtension(QOpenGLExtensions::Sized8Formats); if (caps.gles) caps.instancing = caps.ctxMajor >= 3; // ES 3.0 else caps.instancing = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 3); // 3.3 caps.baseVertex = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2); // 3.2 or ES 3.2 if (caps.gles) caps.compute = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 1); // ES 3.1 else caps.compute = caps.ctxMajor > 4 || (caps.ctxMajor == 4 && caps.ctxMinor >= 3); // 4.3 if (caps.compute) { f->glGetIntegerv(GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS, &caps.maxThreadsPerThreadGroup); GLint tgPerDim[3]; f->glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 0, &tgPerDim[0]); f->glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 1, &tgPerDim[1]); f->glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 2, &tgPerDim[2]); caps.maxThreadGroupsPerDimension = qMin(tgPerDim[0], qMin(tgPerDim[1], tgPerDim[2])); f->glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 0, &caps.maxThreadGroupsX); f->glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 1, &caps.maxThreadGroupsY); f->glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 2, &caps.maxThreadGroupsZ); } if (caps.gles) caps.textureCompareMode = caps.ctxMajor >= 3; // ES 3.0 else caps.textureCompareMode = true; // proper as in ES 3.0 (glMapBufferRange), not the old glMapBuffer // extension(s) (which is not in ES 3.0...messy) caps.properMapBuffer = f->hasOpenGLExtension(QOpenGLExtensions::MapBufferRange); if (caps.gles) caps.nonBaseLevelFramebufferTexture = caps.ctxMajor >= 3; // ES 3.0 else caps.nonBaseLevelFramebufferTexture = true; caps.texelFetch = caps.ctxMajor >= 3; // 3.0 or ES 3.0 caps.intAttributes = caps.ctxMajor >= 3; // 3.0 or ES 3.0 caps.screenSpaceDerivatives = f->hasOpenGLExtension(QOpenGLExtensions::StandardDerivatives); if (caps.gles) caps.multisampledTexture = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 1); // ES 3.1 else caps.multisampledTexture = caps.ctxMajor >= 3; // 3.0 // Program binary support: only the core stuff, do not bother with the old // extensions like GL_OES_get_program_binary if (caps.gles) caps.programBinary = caps.ctxMajor >= 3; // ES 3.0 else caps.programBinary = caps.ctxMajor > 4 || (caps.ctxMajor == 4 && caps.ctxMinor >= 1); // 4.1 if (caps.programBinary) { GLint fmtCount = 0; f->glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &fmtCount); if (fmtCount < 1) caps.programBinary = false; } caps.texture3D = caps.ctxMajor >= 3; // 3.0 if (!caps.gles) { f->glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); f->glEnable(GL_POINT_SPRITE); } // else (with gles) these are always on // Match D3D and others when it comes to seamless cubemap filtering. // ES 3.0+ has this always enabled. (hopefully) // ES 2.0 and GL < 3.2 will not have it. if (!caps.gles && (caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2))) f->glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); nativeHandlesStruct.context = ctx; contextLost = false; return true; } void QRhiGles2::destroy() { if (!f) return; ensureContext(); executeDeferredReleases(); if (vao) { f->glDeleteVertexArrays(1, &vao); vao = 0; } for (uint shader : m_shaderCache) f->glDeleteShader(shader); m_shaderCache.clear(); if (!importedContext) { delete ctx; ctx = nullptr; } f = nullptr; } void QRhiGles2::executeDeferredReleases() { for (int i = releaseQueue.count() - 1; i >= 0; --i) { const QRhiGles2::DeferredReleaseEntry &e(releaseQueue[i]); switch (e.type) { case QRhiGles2::DeferredReleaseEntry::Buffer: f->glDeleteBuffers(1, &e.buffer.buffer); break; case QRhiGles2::DeferredReleaseEntry::Pipeline: f->glDeleteProgram(e.pipeline.program); break; case QRhiGles2::DeferredReleaseEntry::Texture: f->glDeleteTextures(1, &e.texture.texture); break; case QRhiGles2::DeferredReleaseEntry::RenderBuffer: f->glDeleteRenderbuffers(1, &e.renderbuffer.renderbuffer); f->glDeleteRenderbuffers(1, &e.renderbuffer.renderbuffer2); break; case QRhiGles2::DeferredReleaseEntry::TextureRenderTarget: f->glDeleteFramebuffers(1, &e.textureRenderTarget.framebuffer); break; default: Q_UNREACHABLE(); break; } releaseQueue.removeAt(i); } } QList QRhiGles2::supportedSampleCounts() const { if (supportedSampleCountList.isEmpty()) { // 1, 2, 4, 8, ... for (int i = 1; i <= caps.maxSamples; i *= 2) supportedSampleCountList.append(i); } 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) { return new QGles2Buffer(this, type, usage, size); } int QRhiGles2::ubufAlignment() const { // No real uniform buffers are used so no need to pretend there is any // alignment requirement. return 1; } bool QRhiGles2::isYUpInFramebuffer() const { return true; } bool QRhiGles2::isYUpInNDC() const { return true; } bool QRhiGles2::isClipDepthZeroToOne() const { return false; } QMatrix4x4 QRhiGles2::clipSpaceCorrMatrix() const { return QMatrix4x4(); // identity } static inline GLenum toGlCompressedTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags) { const bool srgb = flags.testFlag(QRhiTexture::sRGB); switch (format) { case QRhiTexture::BC1: return srgb ? 0x8C4C : 0x83F0; case QRhiTexture::BC2: return srgb ? 0x8C4E : 0x83F2; case QRhiTexture::BC3: return srgb ? 0x8C4F : 0x83F3; case QRhiTexture::ETC2_RGB8: return srgb ? 0x9275 : 0x9274; case QRhiTexture::ETC2_RGB8A1: return srgb ? 0x9277 : 0x9276; case QRhiTexture::ETC2_RGBA8: return srgb ? 0x9279 : 0x9278; case QRhiTexture::ASTC_4x4: return srgb ? 0x93D0 : 0x93B0; case QRhiTexture::ASTC_5x4: return srgb ? 0x93D1 : 0x93B1; case QRhiTexture::ASTC_5x5: return srgb ? 0x93D2 : 0x93B2; case QRhiTexture::ASTC_6x5: return srgb ? 0x93D3 : 0x93B3; case QRhiTexture::ASTC_6x6: return srgb ? 0x93D4 : 0x93B4; case QRhiTexture::ASTC_8x5: return srgb ? 0x93D5 : 0x93B5; case QRhiTexture::ASTC_8x6: return srgb ? 0x93D6 : 0x93B6; case QRhiTexture::ASTC_8x8: return srgb ? 0x93D7 : 0x93B7; case QRhiTexture::ASTC_10x5: return srgb ? 0x93D8 : 0x93B8; case QRhiTexture::ASTC_10x6: return srgb ? 0x93D9 : 0x93B9; case QRhiTexture::ASTC_10x8: return srgb ? 0x93DA : 0x93BA; case QRhiTexture::ASTC_10x10: return srgb ? 0x93DB : 0x93BB; case QRhiTexture::ASTC_12x10: return srgb ? 0x93DC : 0x93BC; case QRhiTexture::ASTC_12x12: return srgb ? 0x93DD : 0x93BD; default: return 0; // this is reachable, just return an invalid format } } static inline void toGlTextureFormat(QRhiTexture::Format format, const QRhiGles2::Caps &caps, GLenum *glintformat, GLenum *glsizedintformat, GLenum *glformat, GLenum *gltype) { switch (format) { case QRhiTexture::RGBA8: *glintformat = GL_RGBA; *glsizedintformat = caps.rgba8Format ? GL_RGBA8 : GL_RGBA; *glformat = GL_RGBA; *gltype = GL_UNSIGNED_BYTE; break; case QRhiTexture::BGRA8: *glintformat = caps.bgraInternalFormat ? GL_BGRA : GL_RGBA; *glsizedintformat = caps.rgba8Format ? GL_RGBA8 : GL_RGBA; *glformat = GL_BGRA; *gltype = GL_UNSIGNED_BYTE; break; case QRhiTexture::R16: *glintformat = GL_R16; *glsizedintformat = *glintformat; *glformat = GL_RED; *gltype = GL_UNSIGNED_SHORT; break; case QRhiTexture::RG16: *glintformat = GL_RG16; *glsizedintformat = *glintformat; *glformat = GL_RG; *gltype = GL_UNSIGNED_SHORT; break; case QRhiTexture::R8: *glintformat = GL_R8; *glsizedintformat = *glintformat; *glformat = GL_RED; *gltype = GL_UNSIGNED_BYTE; break; case QRhiTexture::RG8: *glintformat = GL_RG8; *glsizedintformat = *glintformat; *glformat = GL_RG; *gltype = GL_UNSIGNED_BYTE; break; case QRhiTexture::RED_OR_ALPHA8: *glintformat = caps.coreProfile ? GL_R8 : GL_ALPHA; *glsizedintformat = *glintformat; *glformat = caps.coreProfile ? GL_RED : GL_ALPHA; *gltype = GL_UNSIGNED_BYTE; break; case QRhiTexture::RGBA16F: *glintformat = GL_RGBA16F; *glsizedintformat = *glintformat; *glformat = GL_RGBA; *gltype = GL_HALF_FLOAT; break; case QRhiTexture::RGBA32F: *glintformat = GL_RGBA32F; *glsizedintformat = *glintformat; *glformat = GL_RGBA; *gltype = GL_FLOAT; break; case QRhiTexture::R16F: *glintformat = GL_R16F; *glsizedintformat = *glintformat; *glformat = GL_RED; *gltype = GL_HALF_FLOAT; break; case QRhiTexture::R32F: *glintformat = GL_R32F; *glsizedintformat = *glintformat; *glformat = GL_RED; *gltype = GL_FLOAT; break; case QRhiTexture::D16: *glintformat = GL_DEPTH_COMPONENT16; *glsizedintformat = *glintformat; *glformat = GL_DEPTH_COMPONENT; *gltype = GL_UNSIGNED_SHORT; break; case QRhiTexture::D24: *glintformat = GL_DEPTH_COMPONENT24; *glsizedintformat = *glintformat; *glformat = GL_DEPTH_COMPONENT; *gltype = GL_UNSIGNED_SHORT; break; case QRhiTexture::D24S8: *glintformat = GL_DEPTH24_STENCIL8; *glsizedintformat = *glintformat; *glformat = GL_DEPTH_STENCIL; *gltype = GL_UNSIGNED_SHORT; break; case QRhiTexture::D32F: *glintformat = GL_DEPTH_COMPONENT32F; *glsizedintformat = *glintformat; *glformat = GL_DEPTH_COMPONENT; *gltype = GL_FLOAT; break; default: Q_UNREACHABLE(); *glintformat = GL_RGBA; *glsizedintformat = caps.rgba8Format ? GL_RGBA8 : GL_RGBA; *glformat = GL_RGBA; *gltype = GL_UNSIGNED_BYTE; break; } } bool QRhiGles2::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const { if (isCompressedFormat(format)) return supportedCompressedFormats.contains(GLint(toGlCompressedTextureFormat(format, flags))); switch (format) { case QRhiTexture::D16: case QRhiTexture::D32F: return caps.depthTexture; case QRhiTexture::D24: return caps.depth24; case QRhiTexture::D24S8: return caps.depth24 && caps.packedDepthStencil; case QRhiTexture::BGRA8: return caps.bgraExternalFormat; case QRhiTexture::R8: return caps.r8Format; case QRhiTexture::RG8: return caps.r8Format; case QRhiTexture::R16: return caps.r16Format; case QRhiTexture::RG16: return caps.r16Format; case QRhiTexture::RGBA16F: case QRhiTexture::RGBA32F: return caps.floatFormats; case QRhiTexture::R16F: case QRhiTexture::R32F: return caps.floatFormats; default: break; } return true; } bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const { switch (feature) { case QRhi::MultisampleTexture: return caps.multisampledTexture; case QRhi::MultisampleRenderBuffer: return caps.msaaRenderBuffer; case QRhi::DebugMarkers: return false; case QRhi::Timestamps: return false; case QRhi::Instancing: return caps.instancing; case QRhi::CustomInstanceStepRate: return false; case QRhi::PrimitiveRestart: return caps.fixedIndexPrimitiveRestart; case QRhi::NonDynamicUniformBuffers: return true; case QRhi::NonFourAlignedEffectiveIndexBufferOffset: return true; case QRhi::NPOTTextureRepeat: return caps.npotTextureFull; case QRhi::RedOrAlpha8IsRed: return caps.coreProfile; case QRhi::ElementIndexUint: return caps.elementIndexUint; case QRhi::Compute: return caps.compute; case QRhi::WideLines: return !caps.coreProfile; case QRhi::VertexShaderPointSize: return true; case QRhi::BaseVertex: return caps.baseVertex; case QRhi::BaseInstance: return false; // not in ES 3.2, so won't bother case QRhi::TriangleFanTopology: return true; case QRhi::ReadBackNonUniformBuffer: return !caps.gles || caps.properMapBuffer; case QRhi::ReadBackNonBaseMipLevel: return caps.nonBaseLevelFramebufferTexture; case QRhi::TexelFetch: return caps.texelFetch; case QRhi::RenderToNonBaseMipLevel: return caps.nonBaseLevelFramebufferTexture; case QRhi::IntAttributes: return caps.intAttributes; case QRhi::ScreenSpaceDerivatives: return caps.screenSpaceDerivatives; case QRhi::ReadBackAnyTextureFormat: return false; case QRhi::PipelineCacheDataLoadSave: return caps.programBinary; case QRhi::ImageDataStride: return !caps.gles || caps.ctxMajor >= 3; case QRhi::RenderBufferImport: return true; case QRhi::ThreeDimensionalTextures: return caps.texture3D; case QRhi::RenderTo3DTextureSlice: return caps.texture3D; default: Q_UNREACHABLE(); return false; } } int QRhiGles2::resourceLimit(QRhi::ResourceLimit limit) const { switch (limit) { case QRhi::TextureSizeMin: return 1; case QRhi::TextureSizeMax: return caps.maxTextureSize; case QRhi::MaxColorAttachments: return caps.maxDrawBuffers; case QRhi::FramesInFlight: // From our perspective. What the GL impl does internally is another // question, but that's out of our hands and does not concern us here. return 1; case QRhi::MaxAsyncReadbackFrames: return 1; case QRhi::MaxThreadGroupsPerDimension: return caps.maxThreadGroupsPerDimension; case QRhi::MaxThreadsPerThreadGroup: return caps.maxThreadsPerThreadGroup; case QRhi::MaxThreadGroupX: return caps.maxThreadGroupsX; case QRhi::MaxThreadGroupY: return caps.maxThreadGroupsY; case QRhi::MaxThreadGroupZ: return caps.maxThreadGroupsZ; default: Q_UNREACHABLE(); return 0; } } const QRhiNativeHandles *QRhiGles2::nativeHandles() { return &nativeHandlesStruct; } QRhiDriverInfo QRhiGles2::driverInfo() const { return driverInfoStruct; } void QRhiGles2::sendVMemStatsToProfiler() { // nothing to do here } bool QRhiGles2::makeThreadLocalNativeContextCurrent() { if (inFrame && !ofr.active) return ensureContext(currentSwapChain->surface); else return ensureContext(); } void QRhiGles2::releaseCachedResources() { if (!ensureContext()) return; for (uint shader : m_shaderCache) f->glDeleteShader(shader); m_shaderCache.clear(); m_pipelineCache.clear(); } bool QRhiGles2::isDeviceLost() const { return contextLost; } struct QGles2PipelineCacheDataHeader { quint32 rhiId; quint32 arch; quint32 programBinaryCount; quint32 dataSize; char driver[240]; }; QByteArray QRhiGles2::pipelineCacheData() { Q_STATIC_ASSERT(sizeof(QGles2PipelineCacheDataHeader) == 256); if (m_pipelineCache.isEmpty()) return QByteArray(); QGles2PipelineCacheDataHeader header; 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())); if (driverStrLen) memcpy(header.driver, driverInfoStruct.deviceName.constData(), driverStrLen); header.driver[driverStrLen] = '\0'; const size_t dataOffset = sizeof(header); size_t dataSize = 0; for (auto it = m_pipelineCache.cbegin(), end = m_pipelineCache.cend(); it != end; ++it) { dataSize += sizeof(quint32) + it.key().size() + sizeof(quint32) + it->data.size() + sizeof(quint32); } QByteArray buf(dataOffset + dataSize, Qt::Uninitialized); char *p = buf.data() + dataOffset; for (auto it = m_pipelineCache.cbegin(), end = m_pipelineCache.cend(); it != end; ++it) { const QByteArray key = it.key(); const QByteArray data = it->data; const quint32 format = it->format; quint32 i = key.size(); memcpy(p, &i, 4); p += 4; memcpy(p, key.constData(), key.size()); p += key.size(); i = data.size(); memcpy(p, &i, 4); p += 4; memcpy(p, data.constData(), data.size()); p += data.size(); memcpy(p, &format, 4); p += 4; } Q_ASSERT(p == buf.data() + dataOffset + dataSize); header.dataSize = quint32(dataSize); memcpy(buf.data(), &header, sizeof(header)); return buf; } void QRhiGles2::setPipelineCacheData(const QByteArray &data) { if (data.isEmpty()) return; const size_t headerSize = sizeof(QGles2PipelineCacheDataHeader); if (data.size() < qsizetype(headerSize)) { qWarning("setPipelineCacheData: Invalid blob size (header incomplete)"); return; } const size_t dataOffset = headerSize; QGles2PipelineCacheDataHeader header; memcpy(&header, data.constData(), headerSize); 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); return; } const quint32 arch = quint32(sizeof(void*)); if (header.arch != arch) { qWarning("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())); if (strncmp(header.driver, driverInfoStruct.deviceName.constData(), driverStrLen)) { qWarning("setPipelineCacheData: OpenGL vendor/renderer/version does not match"); return; } if (data.size() < qsizetype(dataOffset + header.dataSize)) { qWarning("setPipelineCacheData: Invalid blob size (data incomplete)"); return; } m_pipelineCache.clear(); const char *p = data.constData() + dataOffset; for (quint32 i = 0; i < header.programBinaryCount; ++i) { quint32 len = 0; memcpy(&len, p, 4); p += 4; QByteArray key(len, Qt::Uninitialized); memcpy(key.data(), p, len); p += len; memcpy(&len, p, 4); p += 4; QByteArray data(len, Qt::Uninitialized); memcpy(data.data(), p, len); p += len; quint32 format; memcpy(&format, p, 4); p += 4; m_pipelineCache.insert(key, { format, data }); } qCDebug(QRHI_LOG_INFO, "Seeded pipeline cache with %d program binaries", int(m_pipelineCache.count())); } QRhiRenderBuffer *QRhiGles2::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, int sampleCount, QRhiRenderBuffer::Flags flags, QRhiTexture::Format backingFormatHint) { return new QGles2RenderBuffer(this, type, pixelSize, sampleCount, flags, backingFormatHint); } QRhiTexture *QRhiGles2::createTexture(QRhiTexture::Format format, const QSize &pixelSize, int depth, int sampleCount, QRhiTexture::Flags flags) { return new QGles2Texture(this, format, pixelSize, depth, sampleCount, flags); } QRhiSampler *QRhiGles2::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w) { return new QGles2Sampler(this, magFilter, minFilter, mipmapMode, u, v, w); } QRhiTextureRenderTarget *QRhiGles2::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) { return new QGles2TextureRenderTarget(this, desc, flags); } QRhiGraphicsPipeline *QRhiGles2::createGraphicsPipeline() { return new QGles2GraphicsPipeline(this); } QRhiShaderResourceBindings *QRhiGles2::createShaderResourceBindings() { return new QGles2ShaderResourceBindings(this); } QRhiComputePipeline *QRhiGles2::createComputePipeline() { return new QGles2ComputePipeline(this); } void QRhiGles2::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps) { QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, ps); const bool pipelineChanged = cbD->currentGraphicsPipeline != ps || cbD->currentPipelineGeneration != psD->generation; if (pipelineChanged) { cbD->currentGraphicsPipeline = ps; cbD->currentComputePipeline = nullptr; cbD->currentPipelineGeneration = psD->generation; QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::BindGraphicsPipeline; cmd.args.bindGraphicsPipeline.ps = ps; } } void QRhiGles2::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb, int dynamicOffsetCount, const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) { QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); Q_ASSERT(cbD->recordingPass != QGles2CommandBuffer::NoPass); QGles2GraphicsPipeline *gfxPsD = QRHI_RES(QGles2GraphicsPipeline, cbD->currentGraphicsPipeline); QGles2ComputePipeline *compPsD = QRHI_RES(QGles2ComputePipeline, cbD->currentComputePipeline); if (!srb) { if (gfxPsD) srb = gfxPsD->m_shaderResourceBindings; else srb = compPsD->m_shaderResourceBindings; } 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(); switch (b->type) { case QRhiShaderResourceBinding::UniformBuffer: // no BufUniformRead / AccessUniform because no real uniform buffers are used break; case QRhiShaderResourceBinding::SampledTexture: for (int elem = 0; elem < b->u.stex.count; ++elem) { trackedRegisterTexture(&passResTracker, QRHI_RES(QGles2Texture, b->u.stex.texSamplers[elem].tex), QRhiPassResourceTracker::TexSample, QRhiPassResourceTracker::toPassTrackerTextureStage(b->stage)); } break; case QRhiShaderResourceBinding::ImageLoad: case QRhiShaderResourceBinding::ImageStore: case QRhiShaderResourceBinding::ImageLoadStore: { QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.simage.tex); QRhiPassResourceTracker::TextureAccess access; if (b->type == QRhiShaderResourceBinding::ImageLoad) access = QRhiPassResourceTracker::TexStorageLoad; else if (b->type == QRhiShaderResourceBinding::ImageStore) access = QRhiPassResourceTracker::TexStorageStore; else access = QRhiPassResourceTracker::TexStorageLoadStore; trackedRegisterTexture(&passResTracker, texD, access, QRhiPassResourceTracker::toPassTrackerTextureStage(b->stage)); } break; case QRhiShaderResourceBinding::BufferLoad: case QRhiShaderResourceBinding::BufferStore: case QRhiShaderResourceBinding::BufferLoadStore: { QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, b->u.sbuf.buf); QRhiPassResourceTracker::BufferAccess access; if (b->type == QRhiShaderResourceBinding::BufferLoad) access = QRhiPassResourceTracker::BufStorageLoad; else if (b->type == QRhiShaderResourceBinding::BufferStore) access = QRhiPassResourceTracker::BufStorageStore; else access = QRhiPassResourceTracker::BufStorageLoadStore; trackedRegisterBuffer(&passResTracker, bufD, access, QRhiPassResourceTracker::toPassTrackerBufferStage(b->stage)); } break; default: break; } } } bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb); // The Command::BindShaderResources command generated below is what will // cause uniforms to be set (glUniformNxx). This needs some special // handling here in this backend without real uniform buffers, because, // like in other backends, we optimize out the setShaderResources when the // srb that was set before is attempted to be set again on the command // buffer, but that is incorrect if the same srb is now used with another // pipeline. (because that could mean a glUseProgram not followed by // up-to-date glUniform calls, i.e. with GL we have a strong dependency // between the pipeline (== program) and the srb, unlike other APIs) This // is the reason there is a second level of srb(+generation) tracking in // the pipeline objects. if (gfxPsD && (gfxPsD->currentSrb != srb || gfxPsD->currentSrbGeneration != srbD->generation)) { srbChanged = true; gfxPsD->currentSrb = srb; gfxPsD->currentSrbGeneration = srbD->generation; } else if (compPsD && (compPsD->currentSrb != srb || compPsD->currentSrbGeneration != srbD->generation)) { srbChanged = true; compPsD->currentSrb = srb; compPsD->currentSrbGeneration = srbD->generation; } if (srbChanged || cbD->currentSrbGeneration != srbD->generation || srbD->hasDynamicOffset) { if (gfxPsD) { cbD->currentGraphicsSrb = srb; cbD->currentComputeSrb = nullptr; } else { cbD->currentGraphicsSrb = nullptr; cbD->currentComputeSrb = srb; } cbD->currentSrbGeneration = srbD->generation; QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::BindShaderResources; cmd.args.bindShaderResources.maybeGraphicsPs = gfxPsD; cmd.args.bindShaderResources.maybeComputePs = compPsD; cmd.args.bindShaderResources.srb = srb; cmd.args.bindShaderResources.dynamicOffsetCount = 0; if (srbD->hasDynamicOffset) { if (dynamicOffsetCount < QGles2CommandBuffer::MAX_DYNAMIC_OFFSET_COUNT) { cmd.args.bindShaderResources.dynamicOffsetCount = dynamicOffsetCount; uint *p = cmd.args.bindShaderResources.dynamicOffsetPairs; for (int i = 0; i < dynamicOffsetCount; ++i) { const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]); *p++ = uint(dynOfs.first); *p++ = dynOfs.second; } } else { qWarning("Too many dynamic offsets (%d, max is %d)", dynamicOffsetCount, QGles2CommandBuffer::MAX_DYNAMIC_OFFSET_COUNT); } } } } void QRhiGles2::setVertexInput(QRhiCommandBuffer *cb, int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat) { QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]); for (int i = 0; i < bindingCount; ++i) { QRhiBuffer *buf = bindings[i].first; quint32 ofs = bindings[i].second; QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, buf); Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::VertexBuffer)); QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::BindVertexBuffer; cmd.args.bindVertexBuffer.ps = cbD->currentGraphicsPipeline; cmd.args.bindVertexBuffer.buffer = bufD->buffer; cmd.args.bindVertexBuffer.offset = ofs; cmd.args.bindVertexBuffer.binding = startBinding + i; if (cbD->passNeedsResourceTracking) { trackedRegisterBuffer(&passResTracker, bufD, QRhiPassResourceTracker::BufVertexInput, QRhiPassResourceTracker::BufVertexInputStage); } } if (indexBuf) { QGles2Buffer *ibufD = QRHI_RES(QGles2Buffer, indexBuf); Q_ASSERT(ibufD->m_usage.testFlag(QRhiBuffer::IndexBuffer)); QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::BindIndexBuffer; cmd.args.bindIndexBuffer.buffer = ibufD->buffer; cmd.args.bindIndexBuffer.offset = indexOffset; cmd.args.bindIndexBuffer.type = indexFormat == QRhiCommandBuffer::IndexUInt16 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT; if (cbD->passNeedsResourceTracking) { trackedRegisterBuffer(&passResTracker, ibufD, QRhiPassResourceTracker::BufIndexRead, QRhiPassResourceTracker::BufVertexInputStage); } } } void QRhiGles2::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) { QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); const std::array r = viewport.viewport(); // A negative width or height is an error. A negative x or y is not. if (r[2] < 0.0f || r[3] < 0.0f) return; QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::Viewport; cmd.args.viewport.x = r[0]; cmd.args.viewport.y = r[1]; cmd.args.viewport.w = r[2]; cmd.args.viewport.h = r[3]; cmd.args.viewport.d0 = viewport.minDepth(); cmd.args.viewport.d1 = viewport.maxDepth(); } void QRhiGles2::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) { QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); const std::array r = scissor.scissor(); // A negative width or height is an error. A negative x or y is not. if (r[2] < 0 || r[3] < 0) return; QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::Scissor; cmd.args.scissor.x = r[0]; cmd.args.scissor.y = r[1]; cmd.args.scissor.w = r[2]; cmd.args.scissor.h = r[3]; } void QRhiGles2::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) { QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::BlendConstants; cmd.args.blendConstants.r = float(c.redF()); cmd.args.blendConstants.g = float(c.greenF()); cmd.args.blendConstants.b = float(c.blueF()); cmd.args.blendConstants.a = float(c.alphaF()); } void QRhiGles2::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) { QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::StencilRef; cmd.args.stencilRef.ref = refValue; cmd.args.stencilRef.ps = cbD->currentGraphicsPipeline; } void QRhiGles2::draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) { QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::Draw; cmd.args.draw.ps = cbD->currentGraphicsPipeline; cmd.args.draw.vertexCount = vertexCount; cmd.args.draw.firstVertex = firstVertex; cmd.args.draw.instanceCount = instanceCount; cmd.args.draw.baseInstance = firstInstance; } void QRhiGles2::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance) { QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::DrawIndexed; cmd.args.drawIndexed.ps = cbD->currentGraphicsPipeline; cmd.args.drawIndexed.indexCount = indexCount; cmd.args.drawIndexed.firstIndex = firstIndex; cmd.args.drawIndexed.instanceCount = instanceCount; cmd.args.drawIndexed.baseInstance = firstInstance; cmd.args.drawIndexed.baseVertex = vertexOffset; } void QRhiGles2::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) { if (!debugMarkers) return; Q_UNUSED(cb); Q_UNUSED(name); } void QRhiGles2::debugMarkEnd(QRhiCommandBuffer *cb) { if (!debugMarkers) return; Q_UNUSED(cb); } void QRhiGles2::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) { if (!debugMarkers) return; Q_UNUSED(cb); Q_UNUSED(msg); } const QRhiNativeHandles *QRhiGles2::nativeHandles(QRhiCommandBuffer *cb) { Q_UNUSED(cb); return nullptr; } static inline void addBoundaryCommand(QGles2CommandBuffer *cbD, QGles2CommandBuffer::Command::Cmd type) { QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = type; } void QRhiGles2::beginExternal(QRhiCommandBuffer *cb) { if (ofr.active) { Q_ASSERT(!currentSwapChain); if (!ensureContext()) return; } else { Q_ASSERT(currentSwapChain); if (!ensureContext(currentSwapChain->surface)) return; } QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); if (cbD->recordingPass == QGles2CommandBuffer::ComputePass && !cbD->computePassState.writtenResources.isEmpty()) { QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::Barrier; cmd.args.barrier.barriers = GL_ALL_BARRIER_BITS; } executeCommandBuffer(cbD); cbD->resetCommands(); if (vao) f->glBindVertexArray(0); } void QRhiGles2::endExternal(QRhiCommandBuffer *cb) { QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); Q_ASSERT(cbD->commands.isEmpty() && cbD->currentPassResTrackerIndex == -1); cbD->resetCachedState(); if (cbD->recordingPass != QGles2CommandBuffer::NoPass) { // Commands that come after this point need a resource tracker and also // a BarriersForPass command enqueued. (the ones we had from // beginPass() are now gone since beginExternal() processed all that // due to calling executeCommandBuffer()). enqueueBarriersForPass(cbD); } addBoundaryCommand(cbD, QGles2CommandBuffer::Command::ResetFrame); if (cbD->currentTarget) enqueueBindFramebuffer(cbD->currentTarget, cbD); } QRhi::FrameOpResult QRhiGles2::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags) { QGles2SwapChain *swapChainD = QRHI_RES(QGles2SwapChain, swapChain); if (!ensureContext(swapChainD->surface)) return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError; currentSwapChain = swapChainD; QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); QRHI_PROF_F(beginSwapChainFrame(swapChain)); executeDeferredReleases(); swapChainD->cb.resetState(); addBoundaryCommand(&swapChainD->cb, QGles2CommandBuffer::Command::BeginFrame); return QRhi::FrameOpSuccess; } QRhi::FrameOpResult QRhiGles2::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) { QGles2SwapChain *swapChainD = QRHI_RES(QGles2SwapChain, swapChain); Q_ASSERT(currentSwapChain == swapChainD); addBoundaryCommand(&swapChainD->cb, QGles2CommandBuffer::Command::EndFrame); if (!ensureContext(swapChainD->surface)) return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError; executeCommandBuffer(&swapChainD->cb); QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); // this must be done before the swap QRHI_PROF_F(endSwapChainFrame(swapChain, swapChainD->frameCount + 1)); if (swapChainD->surface && !flags.testFlag(QRhi::SkipPresent)) { ctx->swapBuffers(swapChainD->surface); needsMakeCurrent = true; } else { f->glFlush(); } swapChainD->frameCount += 1; currentSwapChain = nullptr; return QRhi::FrameOpSuccess; } QRhi::FrameOpResult QRhiGles2::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags) { if (!ensureContext()) return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError; ofr.active = true; executeDeferredReleases(); ofr.cbWrapper.resetState(); addBoundaryCommand(&ofr.cbWrapper, QGles2CommandBuffer::Command::BeginFrame); *cb = &ofr.cbWrapper; return QRhi::FrameOpSuccess; } QRhi::FrameOpResult QRhiGles2::endOffscreenFrame(QRhi::EndFrameFlags flags) { Q_UNUSED(flags); Q_ASSERT(ofr.active); ofr.active = false; addBoundaryCommand(&ofr.cbWrapper, QGles2CommandBuffer::Command::EndFrame); if (!ensureContext()) return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError; executeCommandBuffer(&ofr.cbWrapper); return QRhi::FrameOpSuccess; } QRhi::FrameOpResult QRhiGles2::finish() { if (inFrame) { if (ofr.active) { Q_ASSERT(!currentSwapChain); Q_ASSERT(ofr.cbWrapper.recordingPass == QGles2CommandBuffer::NoPass); if (!ensureContext()) return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError; executeCommandBuffer(&ofr.cbWrapper); ofr.cbWrapper.resetCommands(); } else { Q_ASSERT(currentSwapChain); Q_ASSERT(currentSwapChain->cb.recordingPass == QGles2CommandBuffer::NoPass); if (!ensureContext(currentSwapChain->surface)) return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError; executeCommandBuffer(¤tSwapChain->cb); currentSwapChain->cb.resetCommands(); } } return QRhi::FrameOpSuccess; } static bool bufferAccessIsWrite(QGles2Buffer::Access access) { return access == QGles2Buffer::AccessStorageWrite || access == QGles2Buffer::AccessStorageReadWrite || access == QGles2Buffer::AccessUpdate; } static bool textureAccessIsWrite(QGles2Texture::Access access) { return access == QGles2Texture::AccessStorageWrite || access == QGles2Texture::AccessStorageReadWrite || access == QGles2Texture::AccessUpdate || access == QGles2Texture::AccessFramebuffer; } void QRhiGles2::trackedBufferBarrier(QGles2CommandBuffer *cbD, QGles2Buffer *bufD, QGles2Buffer::Access access) { Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::NoPass); // this is for resource updates only const QGles2Buffer::Access prevAccess = bufD->usageState.access; if (access == prevAccess) return; if (bufferAccessIsWrite(prevAccess)) { // Generating the minimal barrier set is way too complicated to do // correctly (prevAccess is overwritten so we won't have proper // tracking across multiple passes) so setting all barrier bits will do // for now. QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::Barrier; cmd.args.barrier.barriers = GL_ALL_BARRIER_BITS; } bufD->usageState.access = access; } void QRhiGles2::trackedImageBarrier(QGles2CommandBuffer *cbD, QGles2Texture *texD, QGles2Texture::Access access) { Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::NoPass); // this is for resource updates only const QGles2Texture::Access prevAccess = texD->usageState.access; if (access == prevAccess) return; if (textureAccessIsWrite(prevAccess)) { QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::Barrier; cmd.args.barrier.barriers = GL_ALL_BARRIER_BITS; } texD->usageState.access = access; } void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cbD, int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc) { trackedImageBarrier(cbD, texD, QGles2Texture::AccessUpdate); const bool isCompressed = isCompressedFormat(texD->m_format); const bool isCubeMap = texD->m_flags.testFlag(QRhiTexture::CubeMap); const GLenum faceTargetBase = isCubeMap ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target; const QPoint dp = subresDesc.destinationTopLeft(); const QByteArray rawData = subresDesc.data(); if (!subresDesc.image().isNull()) { QImage img = subresDesc.image(); QSize size = img.size(); 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 = faceTargetBase + uint(layer); cmd.args.subImage.level = level; cmd.args.subImage.dx = dp.x(); cmd.args.subImage.dy = dp.y(); cmd.args.subImage.dz = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional) ? 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); } else if (!rawData.isEmpty() && isCompressed) { const bool is3D = texD->flags().testFlag(QRhiTexture::ThreeDimensional); if ((texD->flags().testFlag(QRhiTexture::UsedAsCompressedAtlas) || is3D) && !texD->zeroInitialized) { // Create on first upload since glCompressedTexImage2D cannot take // nullptr data. We have a rule in the QRhi docs that the first // upload for a compressed texture must cover the entire image, but // that is clearly not ideal when building a texture atlas, or when // having a 3D texture with per-slice data. quint32 byteSize = 0; compressedFormatInfo(texD->m_format, texD->m_pixelSize, nullptr, &byteSize, nullptr); if (is3D) byteSize *= texD->m_depth; QByteArray zeroBuf(byteSize, 0); QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::CompressedImage; cmd.args.compressedImage.target = texD->target; cmd.args.compressedImage.texture = texD->texture; cmd.args.compressedImage.faceTarget = faceTargetBase + uint(layer); 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 : 0; cmd.args.compressedImage.size = byteSize; cmd.args.compressedImage.data = cbD->retainData(zeroBuf); texD->zeroInitialized = true; } const QSize size = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize) : subresDesc.sourceSize(); if (texD->specified || texD->zeroInitialized) { QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::CompressedSubImage; cmd.args.compressedSubImage.target = texD->target; cmd.args.compressedSubImage.texture = texD->texture; cmd.args.compressedSubImage.faceTarget = faceTargetBase + uint(layer); cmd.args.compressedSubImage.level = level; cmd.args.compressedSubImage.dx = dp.x(); cmd.args.compressedSubImage.dy = dp.y(); cmd.args.compressedSubImage.dz = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional) ? layer : 0; cmd.args.compressedSubImage.w = size.width(); cmd.args.compressedSubImage.h = size.height(); cmd.args.compressedSubImage.glintformat = texD->glintformat; cmd.args.compressedSubImage.size = rawData.size(); cmd.args.compressedSubImage.data = cbD->retainData(rawData); } else { QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::CompressedImage; cmd.args.compressedImage.target = texD->target; cmd.args.compressedImage.texture = texD->texture; cmd.args.compressedImage.faceTarget = faceTargetBase + uint(layer); 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 : 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 = faceTargetBase + uint(layer); cmd.args.subImage.level = level; cmd.args.subImage.dx = dp.x(); cmd.args.subImage.dy = dp.y(); cmd.args.subImage.dz = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional) ? 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 aligment // 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); } else { qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level); } } void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) { QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates); for (int opIdx = 0; opIdx < ud->activeBufferOpCount; ++opIdx) { const QRhiResourceUpdateBatchPrivate::BufferOp &u(ud->bufferOps[opIdx]); if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) { QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf); Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) { memcpy(bufD->data.data() + u.offset, u.data.constData(), size_t(u.data.size())); } else { trackedBufferBarrier(cbD, bufD, QGles2Buffer::AccessUpdate); QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::BufferSubData; cmd.args.bufferSubData.target = bufD->targetForDataOps; cmd.args.bufferSubData.buffer = bufD->buffer; cmd.args.bufferSubData.offset = u.offset; cmd.args.bufferSubData.size = u.data.size(); cmd.args.bufferSubData.data = cbD->retainBufferData(u.data); } } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) { QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf); Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) { memcpy(bufD->data.data() + u.offset, u.data.constData(), size_t(u.data.size())); } else { trackedBufferBarrier(cbD, bufD, QGles2Buffer::AccessUpdate); QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::BufferSubData; cmd.args.bufferSubData.target = bufD->targetForDataOps; cmd.args.bufferSubData.buffer = bufD->buffer; cmd.args.bufferSubData.offset = u.offset; cmd.args.bufferSubData.size = u.data.size(); cmd.args.bufferSubData.data = cbD->retainBufferData(u.data); } } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) { QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf); if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) { u.result->data.resize(u.readSize); memcpy(u.result->data.data(), bufD->data.constData() + u.offset, size_t(u.readSize)); if (u.result->completed) u.result->completed(); } else { QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::GetBufferSubData; cmd.args.getBufferSubData.result = u.result; cmd.args.getBufferSubData.target = bufD->targetForDataOps; cmd.args.getBufferSubData.buffer = bufD->buffer; cmd.args.getBufferSubData.offset = u.offset; cmd.args.getBufferSubData.size = u.readSize; } } } for (int opIdx = 0; opIdx < ud->activeTextureOpCount; ++opIdx) { 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 level = 0; level < QRhi::MAX_MIP_LEVELS; ++level) { for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level])) enqueueSubresUpload(texD, cbD, layer, level, subresDesc); } } texD->specified = true; } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) { Q_ASSERT(u.src && u.dst); QGles2Texture *srcD = QRHI_RES(QGles2Texture, u.src); QGles2Texture *dstD = QRHI_RES(QGles2Texture, u.dst); trackedImageBarrier(cbD, srcD, QGles2Texture::AccessRead); trackedImageBarrier(cbD, dstD, QGles2Texture::AccessUpdate); const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize); const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize(); // do not translate coordinates, even if sp is bottom-left from gl's pov const QPoint sp = u.desc.sourceTopLeft(); const QPoint dp = u.desc.destinationTopLeft(); const GLenum srcFaceTargetBase = srcD->m_flags.testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : srcD->target; const GLenum dstFaceTargetBase = dstD->m_flags.testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : dstD->target; QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::CopyTex; cmd.args.copyTex.srcTarget = srcD->target; cmd.args.copyTex.srcFaceTarget = srcFaceTargetBase + uint(u.desc.sourceLayer()); cmd.args.copyTex.srcTexture = srcD->texture; cmd.args.copyTex.srcLevel = u.desc.sourceLevel(); cmd.args.copyTex.srcX = sp.x(); cmd.args.copyTex.srcY = sp.y(); cmd.args.copyTex.srcZ = srcD->m_flags.testFlag(QRhiTexture::ThreeDimensional) ? u.desc.sourceLayer() : 0; cmd.args.copyTex.dstTarget = dstD->target; cmd.args.copyTex.dstFaceTarget = dstFaceTargetBase + uint(u.desc.destinationLayer()); 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.dstZ = dstD->m_flags.testFlag(QRhiTexture::ThreeDimensional) ? u.desc.destinationLayer() : 0; cmd.args.copyTex.w = copySize.width(); cmd.args.copyTex.h = copySize.height(); } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) { QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::ReadPixels; cmd.args.readPixels.result = u.result; QGles2Texture *texD = QRHI_RES(QGles2Texture, u.rb.texture()); if (texD) trackedImageBarrier(cbD, texD, QGles2Texture::AccessRead); cmd.args.readPixels.texture = texD ? texD->texture : 0; cmd.args.readPixels.slice3D = -1; if (texD) { const QSize readImageSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize); cmd.args.readPixels.w = readImageSize.width(); cmd.args.readPixels.h = readImageSize.height(); cmd.args.readPixels.format = texD->m_format; if (texD->m_flags.testFlag(QRhiTexture::ThreeDimensional)) { cmd.args.readPixels.readTarget = texD->target; cmd.args.readPixels.slice3D = u.rb.layer(); } else { const GLenum faceTargetBase = texD->m_flags.testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target; cmd.args.readPixels.readTarget = faceTargetBase + uint(u.rb.layer()); } cmd.args.readPixels.level = u.rb.level(); } } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) { QGles2Texture *texD = QRHI_RES(QGles2Texture, u.dst); trackedImageBarrier(cbD, texD, QGles2Texture::AccessFramebuffer); QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::GenMip; cmd.args.genMip.target = texD->target; cmd.args.genMip.texture = texD->texture; } } ud->free(); } static inline GLenum toGlTopology(QRhiGraphicsPipeline::Topology t) { switch (t) { case QRhiGraphicsPipeline::Triangles: return GL_TRIANGLES; case QRhiGraphicsPipeline::TriangleStrip: return GL_TRIANGLE_STRIP; case QRhiGraphicsPipeline::TriangleFan: return GL_TRIANGLE_FAN; case QRhiGraphicsPipeline::Lines: return GL_LINES; case QRhiGraphicsPipeline::LineStrip: return GL_LINE_STRIP; case QRhiGraphicsPipeline::Points: return GL_POINTS; default: Q_UNREACHABLE(); return GL_TRIANGLES; } } static inline GLenum toGlCullMode(QRhiGraphicsPipeline::CullMode c) { switch (c) { case QRhiGraphicsPipeline::Front: return GL_FRONT; case QRhiGraphicsPipeline::Back: return GL_BACK; default: Q_UNREACHABLE(); return GL_BACK; } } static inline GLenum toGlFrontFace(QRhiGraphicsPipeline::FrontFace f) { switch (f) { case QRhiGraphicsPipeline::CCW: return GL_CCW; case QRhiGraphicsPipeline::CW: return GL_CW; default: Q_UNREACHABLE(); return GL_CCW; } } static inline GLenum toGlBlendFactor(QRhiGraphicsPipeline::BlendFactor f) { switch (f) { case QRhiGraphicsPipeline::Zero: return GL_ZERO; case QRhiGraphicsPipeline::One: return GL_ONE; case QRhiGraphicsPipeline::SrcColor: return GL_SRC_COLOR; case QRhiGraphicsPipeline::OneMinusSrcColor: return GL_ONE_MINUS_SRC_COLOR; case QRhiGraphicsPipeline::DstColor: return GL_DST_COLOR; case QRhiGraphicsPipeline::OneMinusDstColor: return GL_ONE_MINUS_DST_COLOR; case QRhiGraphicsPipeline::SrcAlpha: return GL_SRC_ALPHA; case QRhiGraphicsPipeline::OneMinusSrcAlpha: return GL_ONE_MINUS_SRC_ALPHA; case QRhiGraphicsPipeline::DstAlpha: return GL_DST_ALPHA; case QRhiGraphicsPipeline::OneMinusDstAlpha: return GL_ONE_MINUS_DST_ALPHA; case QRhiGraphicsPipeline::ConstantColor: return GL_CONSTANT_COLOR; case QRhiGraphicsPipeline::OneMinusConstantColor: return GL_ONE_MINUS_CONSTANT_COLOR; case QRhiGraphicsPipeline::ConstantAlpha: return GL_CONSTANT_ALPHA; case QRhiGraphicsPipeline::OneMinusConstantAlpha: return GL_ONE_MINUS_CONSTANT_ALPHA; case QRhiGraphicsPipeline::SrcAlphaSaturate: return GL_SRC_ALPHA_SATURATE; case QRhiGraphicsPipeline::Src1Color: case QRhiGraphicsPipeline::OneMinusSrc1Color: case QRhiGraphicsPipeline::Src1Alpha: case QRhiGraphicsPipeline::OneMinusSrc1Alpha: qWarning("Unsupported blend factor %d", f); return GL_ZERO; default: Q_UNREACHABLE(); return GL_ZERO; } } static inline GLenum toGlBlendOp(QRhiGraphicsPipeline::BlendOp op) { switch (op) { case QRhiGraphicsPipeline::Add: return GL_FUNC_ADD; case QRhiGraphicsPipeline::Subtract: return GL_FUNC_SUBTRACT; case QRhiGraphicsPipeline::ReverseSubtract: return GL_FUNC_REVERSE_SUBTRACT; case QRhiGraphicsPipeline::Min: return GL_MIN; case QRhiGraphicsPipeline::Max: return GL_MAX; default: Q_UNREACHABLE(); return GL_FUNC_ADD; } } static inline GLenum toGlCompareOp(QRhiGraphicsPipeline::CompareOp op) { switch (op) { case QRhiGraphicsPipeline::Never: return GL_NEVER; case QRhiGraphicsPipeline::Less: return GL_LESS; case QRhiGraphicsPipeline::Equal: return GL_EQUAL; case QRhiGraphicsPipeline::LessOrEqual: return GL_LEQUAL; case QRhiGraphicsPipeline::Greater: return GL_GREATER; case QRhiGraphicsPipeline::NotEqual: return GL_NOTEQUAL; case QRhiGraphicsPipeline::GreaterOrEqual: return GL_GEQUAL; case QRhiGraphicsPipeline::Always: return GL_ALWAYS; default: Q_UNREACHABLE(); return GL_ALWAYS; } } static inline GLenum toGlStencilOp(QRhiGraphicsPipeline::StencilOp op) { switch (op) { case QRhiGraphicsPipeline::StencilZero: return GL_ZERO; case QRhiGraphicsPipeline::Keep: return GL_KEEP; case QRhiGraphicsPipeline::Replace: return GL_REPLACE; case QRhiGraphicsPipeline::IncrementAndClamp: return GL_INCR; case QRhiGraphicsPipeline::DecrementAndClamp: return GL_DECR; case QRhiGraphicsPipeline::Invert: return GL_INVERT; case QRhiGraphicsPipeline::IncrementAndWrap: return GL_INCR_WRAP; case QRhiGraphicsPipeline::DecrementAndWrap: return GL_DECR_WRAP; default: Q_UNREACHABLE(); return GL_KEEP; } } static inline GLenum toGlMinFilter(QRhiSampler::Filter f, QRhiSampler::Filter m) { switch (f) { case QRhiSampler::Nearest: if (m == QRhiSampler::None) return GL_NEAREST; else return m == QRhiSampler::Nearest ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_LINEAR; case QRhiSampler::Linear: if (m == QRhiSampler::None) return GL_LINEAR; else return m == QRhiSampler::Nearest ? GL_LINEAR_MIPMAP_NEAREST : GL_LINEAR_MIPMAP_LINEAR; default: Q_UNREACHABLE(); return GL_LINEAR; } } static inline GLenum toGlMagFilter(QRhiSampler::Filter f) { switch (f) { case QRhiSampler::Nearest: return GL_NEAREST; case QRhiSampler::Linear: return GL_LINEAR; default: Q_UNREACHABLE(); return GL_LINEAR; } } static inline GLenum toGlWrapMode(QRhiSampler::AddressMode m) { switch (m) { case QRhiSampler::Repeat: return GL_REPEAT; case QRhiSampler::ClampToEdge: return GL_CLAMP_TO_EDGE; case QRhiSampler::Mirror: return GL_MIRRORED_REPEAT; default: Q_UNREACHABLE(); return GL_CLAMP_TO_EDGE; } } static inline GLenum toGlTextureCompareFunc(QRhiSampler::CompareOp op) { switch (op) { case QRhiSampler::Never: return GL_NEVER; case QRhiSampler::Less: return GL_LESS; case QRhiSampler::Equal: return GL_EQUAL; case QRhiSampler::LessOrEqual: return GL_LEQUAL; case QRhiSampler::Greater: return GL_GREATER; case QRhiSampler::NotEqual: return GL_NOTEQUAL; case QRhiSampler::GreaterOrEqual: return GL_GEQUAL; case QRhiSampler::Always: return GL_ALWAYS; default: Q_UNREACHABLE(); return GL_NEVER; } } static inline QGles2Buffer::Access toGlAccess(QRhiPassResourceTracker::BufferAccess access) { switch (access) { case QRhiPassResourceTracker::BufVertexInput: return QGles2Buffer::AccessVertex; case QRhiPassResourceTracker::BufIndexRead: return QGles2Buffer::AccessIndex; case QRhiPassResourceTracker::BufUniformRead: return QGles2Buffer::AccessUniform; case QRhiPassResourceTracker::BufStorageLoad: return QGles2Buffer::AccessStorageRead; case QRhiPassResourceTracker::BufStorageStore: return QGles2Buffer::AccessStorageWrite; case QRhiPassResourceTracker::BufStorageLoadStore: return QGles2Buffer::AccessStorageReadWrite; default: Q_UNREACHABLE(); break; } return QGles2Buffer::AccessNone; } static inline QRhiPassResourceTracker::UsageState toPassTrackerUsageState(const QGles2Buffer::UsageState &bufUsage) { QRhiPassResourceTracker::UsageState u; u.layout = 0; // N/A u.access = bufUsage.access; u.stage = 0; // N/A return u; } static inline QGles2Texture::Access toGlAccess(QRhiPassResourceTracker::TextureAccess access) { switch (access) { case QRhiPassResourceTracker::TexSample: return QGles2Texture::AccessSample; case QRhiPassResourceTracker::TexColorOutput: return QGles2Texture::AccessFramebuffer; case QRhiPassResourceTracker::TexDepthOutput: return QGles2Texture::AccessFramebuffer; case QRhiPassResourceTracker::TexStorageLoad: return QGles2Texture::AccessStorageRead; case QRhiPassResourceTracker::TexStorageStore: return QGles2Texture::AccessStorageWrite; case QRhiPassResourceTracker::TexStorageLoadStore: return QGles2Texture::AccessStorageReadWrite; default: Q_UNREACHABLE(); break; } return QGles2Texture::AccessNone; } static inline QRhiPassResourceTracker::UsageState toPassTrackerUsageState(const QGles2Texture::UsageState &texUsage) { QRhiPassResourceTracker::UsageState u; u.layout = 0; // N/A u.access = texUsage.access; u.stage = 0; // N/A return u; } void QRhiGles2::trackedRegisterBuffer(QRhiPassResourceTracker *passResTracker, QGles2Buffer *bufD, QRhiPassResourceTracker::BufferAccess access, QRhiPassResourceTracker::BufferStage stage) { QGles2Buffer::UsageState &u(bufD->usageState); passResTracker->registerBuffer(bufD, 0, &access, &stage, toPassTrackerUsageState(u)); u.access = toGlAccess(access); } void QRhiGles2::trackedRegisterTexture(QRhiPassResourceTracker *passResTracker, QGles2Texture *texD, QRhiPassResourceTracker::TextureAccess access, QRhiPassResourceTracker::TextureStage stage) { QGles2Texture::UsageState &u(texD->usageState); passResTracker->registerTexture(texD, &access, &stage, toPassTrackerUsageState(u)); u.access = toGlAccess(access); } struct CommandBufferExecTrackedState { GLenum indexType = GL_UNSIGNED_SHORT; quint32 indexStride = sizeof(quint16); quint32 indexOffset = 0; GLuint currentArrayBuffer = 0; GLuint currentElementArrayBuffer = 0; struct { QRhiGraphicsPipeline *ps = nullptr; GLuint buffer = 0; quint32 offset = 0; int binding = 0; } lastBindVertexBuffer; static const int TRACKED_ATTRIB_COUNT = 16; bool enabledAttribArrays[TRACKED_ATTRIB_COUNT] = {}; bool nonzeroAttribDivisor[TRACKED_ATTRIB_COUNT] = {}; bool instancedAttributesUsed = false; int maxUntrackedInstancedAttribute = 0; }; // Helper that must be used in executeCommandBuffer() whenever changing the // ARRAY or ELEMENT_ARRAY buffer binding outside of Command::BindVertexBuffer // and Command::BindIndexBuffer. static inline void bindVertexIndexBufferWithStateReset(CommandBufferExecTrackedState *state, QOpenGLExtensions *f, GLenum target, GLuint buffer) { state->currentArrayBuffer = 0; state->currentElementArrayBuffer = 0; state->lastBindVertexBuffer.buffer = 0; f->glBindBuffer(target, buffer); } void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) { CommandBufferExecTrackedState state; QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); for (auto it = cbD->commands.cbegin(), end = cbD->commands.cend(); it != end; ++it) { const QGles2CommandBuffer::Command &cmd(*it); switch (cmd.cmd) { case QGles2CommandBuffer::Command::BeginFrame: if (caps.coreProfile) { if (!vao) f->glGenVertexArrays(1, &vao); f->glBindVertexArray(vao); } break; case QGles2CommandBuffer::Command::EndFrame: if (state.instancedAttributesUsed) { for (int i = 0; i < CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT; ++i) { if (state.nonzeroAttribDivisor[i]) f->glVertexAttribDivisor(GLuint(i), 0); } for (int i = CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT; i <= state.maxUntrackedInstancedAttribute; ++i) f->glVertexAttribDivisor(GLuint(i), 0); state.instancedAttributesUsed = false; } if (vao) f->glBindVertexArray(0); break; case QGles2CommandBuffer::Command::ResetFrame: if (vao) f->glBindVertexArray(vao); break; case QGles2CommandBuffer::Command::Viewport: f->glViewport(GLint(cmd.args.viewport.x), GLint(cmd.args.viewport.y), GLsizei(cmd.args.viewport.w), GLsizei(cmd.args.viewport.h)); f->glDepthRangef(cmd.args.viewport.d0, cmd.args.viewport.d1); break; case QGles2CommandBuffer::Command::Scissor: f->glScissor(cmd.args.scissor.x, cmd.args.scissor.y, cmd.args.scissor.w, cmd.args.scissor.h); break; case QGles2CommandBuffer::Command::BlendConstants: f->glBlendColor(cmd.args.blendConstants.r, cmd.args.blendConstants.g, cmd.args.blendConstants.b, cmd.args.blendConstants.a); break; case QGles2CommandBuffer::Command::StencilRef: { QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.stencilRef.ps); if (psD) { const GLint ref = GLint(cmd.args.stencilRef.ref); f->glStencilFuncSeparate(GL_FRONT, toGlCompareOp(psD->m_stencilFront.compareOp), ref, psD->m_stencilReadMask); f->glStencilFuncSeparate(GL_BACK, toGlCompareOp(psD->m_stencilBack.compareOp), ref, psD->m_stencilReadMask); cbD->graphicsPassState.dynamic.stencilRef = ref; } else { qWarning("No graphics pipeline active for setStencilRef; ignored"); } } break; case QGles2CommandBuffer::Command::BindVertexBuffer: { QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.bindVertexBuffer.ps); if (psD) { if (state.lastBindVertexBuffer.ps == psD && state.lastBindVertexBuffer.buffer == cmd.args.bindVertexBuffer.buffer && state.lastBindVertexBuffer.offset == cmd.args.bindVertexBuffer.offset && state.lastBindVertexBuffer.binding == cmd.args.bindVertexBuffer.binding) { // The pipeline and so the vertex input layout is // immutable, no point in issuing the exact same set of // glVertexAttribPointer again and again for the same buffer. break; } state.lastBindVertexBuffer.ps = psD; state.lastBindVertexBuffer.buffer = cmd.args.bindVertexBuffer.buffer; state.lastBindVertexBuffer.offset = cmd.args.bindVertexBuffer.offset; state.lastBindVertexBuffer.binding = cmd.args.bindVertexBuffer.binding; if (cmd.args.bindVertexBuffer.buffer != state.currentArrayBuffer) { state.currentArrayBuffer = cmd.args.bindVertexBuffer.buffer; // we do not support more than one vertex buffer f->glBindBuffer(GL_ARRAY_BUFFER, state.currentArrayBuffer); } for (auto it = psD->m_vertexInputLayout.cbeginAttributes(), itEnd = psD->m_vertexInputLayout.cendAttributes(); it != itEnd; ++it) { const int bindingIdx = it->binding(); if (bindingIdx != cmd.args.bindVertexBuffer.binding) continue; const QRhiVertexInputBinding *inputBinding = psD->m_vertexInputLayout.bindingAt(bindingIdx); const int stride = int(inputBinding->stride()); int size = 1; GLenum type = GL_FLOAT; bool normalize = false; switch (it->format()) { case QRhiVertexInputAttribute::Float4: type = GL_FLOAT; size = 4; break; case QRhiVertexInputAttribute::Float3: type = GL_FLOAT; size = 3; break; case QRhiVertexInputAttribute::Float2: type = GL_FLOAT; size = 2; break; case QRhiVertexInputAttribute::Float: type = GL_FLOAT; size = 1; break; case QRhiVertexInputAttribute::UNormByte4: type = GL_UNSIGNED_BYTE; normalize = true; size = 4; break; case QRhiVertexInputAttribute::UNormByte2: type = GL_UNSIGNED_BYTE; normalize = true; size = 2; break; case QRhiVertexInputAttribute::UNormByte: type = GL_UNSIGNED_BYTE; normalize = true; size = 1; break; case QRhiVertexInputAttribute::UInt4: type = GL_UNSIGNED_INT; size = 4; break; case QRhiVertexInputAttribute::UInt3: type = GL_UNSIGNED_INT; size = 3; break; case QRhiVertexInputAttribute::UInt2: type = GL_UNSIGNED_INT; size = 2; break; case QRhiVertexInputAttribute::UInt: type = GL_UNSIGNED_INT; size = 1; break; case QRhiVertexInputAttribute::SInt4: type = GL_INT; size = 4; break; case QRhiVertexInputAttribute::SInt3: type = GL_INT; size = 3; break; case QRhiVertexInputAttribute::SInt2: type = GL_INT; size = 2; break; case QRhiVertexInputAttribute::SInt: type = GL_INT; size = 1; break; default: break; } const int locationIdx = it->location(); quint32 ofs = it->offset() + cmd.args.bindVertexBuffer.offset; if (type == GL_UNSIGNED_INT || type == GL_INT) { if (caps.intAttributes) { f->glVertexAttribIPointer(GLuint(locationIdx), size, type, stride, reinterpret_cast(quintptr(ofs))); } else { qWarning("Current RHI backend does not support IntAttributes. Check supported features."); // This is a trick to disable this attribute if (locationIdx < CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT) state.enabledAttribArrays[locationIdx] = true; } } else { f->glVertexAttribPointer(GLuint(locationIdx), size, type, normalize, stride, reinterpret_cast(quintptr(ofs))); } if (locationIdx >= CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT || !state.enabledAttribArrays[locationIdx]) { if (locationIdx < CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT) state.enabledAttribArrays[locationIdx] = true; f->glEnableVertexAttribArray(GLuint(locationIdx)); } if (inputBinding->classification() == QRhiVertexInputBinding::PerInstance && caps.instancing) { f->glVertexAttribDivisor(GLuint(locationIdx), GLuint(inputBinding->instanceStepRate())); if (Q_LIKELY(locationIdx < CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT)) state.nonzeroAttribDivisor[locationIdx] = true; else state.maxUntrackedInstancedAttribute = qMax(state.maxUntrackedInstancedAttribute, locationIdx); state.instancedAttributesUsed = true; } else if ((locationIdx < CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT && state.nonzeroAttribDivisor[locationIdx]) || Q_UNLIKELY(locationIdx >= CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT && locationIdx <= state.maxUntrackedInstancedAttribute)) { f->glVertexAttribDivisor(GLuint(locationIdx), 0); if (locationIdx < CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT) state.nonzeroAttribDivisor[locationIdx] = false; } } } else { qWarning("No graphics pipeline active for setVertexInput; ignored"); } } break; case QGles2CommandBuffer::Command::BindIndexBuffer: state.indexType = cmd.args.bindIndexBuffer.type; state.indexStride = state.indexType == GL_UNSIGNED_SHORT ? sizeof(quint16) : sizeof(quint32); state.indexOffset = cmd.args.bindIndexBuffer.offset; if (state.currentElementArrayBuffer != cmd.args.bindIndexBuffer.buffer) { state.currentElementArrayBuffer = cmd.args.bindIndexBuffer.buffer; f->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, state.currentElementArrayBuffer); } break; case QGles2CommandBuffer::Command::Draw: { QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.draw.ps); if (psD) { if (cmd.args.draw.instanceCount == 1 || !caps.instancing) { f->glDrawArrays(psD->drawMode, GLint(cmd.args.draw.firstVertex), GLsizei(cmd.args.draw.vertexCount)); } else { f->glDrawArraysInstanced(psD->drawMode, GLint(cmd.args.draw.firstVertex), GLsizei(cmd.args.draw.vertexCount), GLsizei(cmd.args.draw.instanceCount)); } } else { qWarning("No graphics pipeline active for draw; ignored"); } } break; case QGles2CommandBuffer::Command::DrawIndexed: { QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.drawIndexed.ps); if (psD) { const GLvoid *ofs = reinterpret_cast( quintptr(cmd.args.drawIndexed.firstIndex * state.indexStride + state.indexOffset)); if (cmd.args.drawIndexed.instanceCount == 1 || !caps.instancing) { if (cmd.args.drawIndexed.baseVertex != 0 && caps.baseVertex) { f->glDrawElementsBaseVertex(psD->drawMode, GLsizei(cmd.args.drawIndexed.indexCount), state.indexType, ofs, cmd.args.drawIndexed.baseVertex); } else { f->glDrawElements(psD->drawMode, GLsizei(cmd.args.drawIndexed.indexCount), state.indexType, ofs); } } else { if (cmd.args.drawIndexed.baseVertex != 0 && caps.baseVertex) { f->glDrawElementsInstancedBaseVertex(psD->drawMode, GLsizei(cmd.args.drawIndexed.indexCount), state.indexType, ofs, GLsizei(cmd.args.drawIndexed.instanceCount), cmd.args.drawIndexed.baseVertex); } else { f->glDrawElementsInstanced(psD->drawMode, GLsizei(cmd.args.drawIndexed.indexCount), state.indexType, ofs, GLsizei(cmd.args.drawIndexed.instanceCount)); } } } else { qWarning("No graphics pipeline active for drawIndexed; ignored"); } } break; case QGles2CommandBuffer::Command::BindGraphicsPipeline: executeBindGraphicsPipeline(cbD, QRHI_RES(QGles2GraphicsPipeline, cmd.args.bindGraphicsPipeline.ps)); break; case QGles2CommandBuffer::Command::BindShaderResources: bindShaderResources(cbD, cmd.args.bindShaderResources.maybeGraphicsPs, cmd.args.bindShaderResources.maybeComputePs, cmd.args.bindShaderResources.srb, cmd.args.bindShaderResources.dynamicOffsetPairs, cmd.args.bindShaderResources.dynamicOffsetCount); break; case QGles2CommandBuffer::Command::BindFramebuffer: if (cmd.args.bindFramebuffer.fbo) { f->glBindFramebuffer(GL_FRAMEBUFFER, cmd.args.bindFramebuffer.fbo); if (caps.maxDrawBuffers > 1) { const int colorAttCount = cmd.args.bindFramebuffer.colorAttCount; QVarLengthArray bufs; for (int i = 0; i < colorAttCount; ++i) bufs.append(GL_COLOR_ATTACHMENT0 + uint(i)); f->glDrawBuffers(colorAttCount, bufs.constData()); } } else { f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject()); } if (caps.srgbCapableDefaultFramebuffer) { 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); if (cmd.args.clear.mask & GL_COLOR_BUFFER_BIT) { f->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); f->glClearColor(cmd.args.clear.c[0], cmd.args.clear.c[1], cmd.args.clear.c[2], cmd.args.clear.c[3]); } if (cmd.args.clear.mask & GL_DEPTH_BUFFER_BIT) { f->glDepthMask(GL_TRUE); f->glClearDepthf(cmd.args.clear.d); } if (cmd.args.clear.mask & GL_STENCIL_BUFFER_BIT) 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; case QGles2CommandBuffer::Command::BufferSubData: bindVertexIndexBufferWithStateReset(&state, f, cmd.args.bufferSubData.target, cmd.args.bufferSubData.buffer); f->glBufferSubData(cmd.args.bufferSubData.target, cmd.args.bufferSubData.offset, cmd.args.bufferSubData.size, cmd.args.bufferSubData.data); break; case QGles2CommandBuffer::Command::GetBufferSubData: { QRhiBufferReadbackResult *result = cmd.args.getBufferSubData.result; bindVertexIndexBufferWithStateReset(&state, f, cmd.args.getBufferSubData.target, cmd.args.getBufferSubData.buffer); if (caps.gles) { if (caps.properMapBuffer) { void *p = f->glMapBufferRange(cmd.args.getBufferSubData.target, cmd.args.getBufferSubData.offset, cmd.args.getBufferSubData.size, GL_MAP_READ_BIT); if (p) { result->data.resize(cmd.args.getBufferSubData.size); memcpy(result->data.data(), p, size_t(cmd.args.getBufferSubData.size)); f->glUnmapBuffer(cmd.args.getBufferSubData.target); } } } else { result->data.resize(cmd.args.getBufferSubData.size); f->glGetBufferSubData(cmd.args.getBufferSubData.target, cmd.args.getBufferSubData.offset, cmd.args.getBufferSubData.size, result->data.data()); } if (result->completed) result->completed(); } break; case QGles2CommandBuffer::Command::CopyTex: { GLuint fbo; f->glGenFramebuffers(1, &fbo); f->glBindFramebuffer(GL_FRAMEBUFFER, fbo); if (cmd.args.copyTex.srcTarget == GL_TEXTURE_3D) { f->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.copyTex.srcTexture, cmd.args.copyTex.srcLevel, cmd.args.copyTex.srcZ); } else { f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.copyTex.srcFaceTarget, cmd.args.copyTex.srcTexture, cmd.args.copyTex.srcLevel); } f->glBindTexture(cmd.args.copyTex.dstTarget, cmd.args.copyTex.dstTexture); if (cmd.args.copyTex.dstTarget == GL_TEXTURE_3D) { f->glCopyTexSubImage3D(cmd.args.copyTex.dstTarget, cmd.args.copyTex.dstLevel, 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 { f->glCopyTexSubImage2D(cmd.args.copyTex.dstFaceTarget, cmd.args.copyTex.dstLevel, cmd.args.copyTex.dstX, cmd.args.copyTex.dstY, cmd.args.copyTex.srcX, cmd.args.copyTex.srcY, cmd.args.copyTex.w, cmd.args.copyTex.h); } f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject()); f->glDeleteFramebuffers(1, &fbo); } break; case QGles2CommandBuffer::Command::ReadPixels: { QRhiReadbackResult *result = cmd.args.readPixels.result; GLuint tex = cmd.args.readPixels.texture; GLuint fbo = 0; int mipLevel = 0; if (tex) { result->pixelSize = QSize(cmd.args.readPixels.w, cmd.args.readPixels.h); result->format = cmd.args.readPixels.format; mipLevel = cmd.args.readPixels.level; if (mipLevel == 0 || caps.nonBaseLevelFramebufferTexture) { f->glGenFramebuffers(1, &fbo); f->glBindFramebuffer(GL_FRAMEBUFFER, fbo); if (cmd.args.readPixels.slice3D >= 0) { f->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, mipLevel, cmd.args.readPixels.slice3D); } else { f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.readPixels.readTarget, tex, mipLevel); } } } else { result->pixelSize = currentSwapChain->pixelSize; result->format = QRhiTexture::RGBA8; // readPixels handles multisample resolving implicitly } const int w = result->pixelSize.width(); const int h = result->pixelSize.height(); if (mipLevel == 0 || caps.nonBaseLevelFramebufferTexture) { // With GLES, GL_RGBA is the only mandated readback format, so stick with it. // (and that's why we return false for the ReadBackAnyTextureFormat feature) if (result->format == QRhiTexture::R8 || result->format == QRhiTexture::RED_OR_ALPHA8) { result->data.resize(w * h); QByteArray tmpBuf; tmpBuf.resize(w * h * 4); f->glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, tmpBuf.data()); const quint8 *srcBase = reinterpret_cast(tmpBuf.constData()); quint8 *dstBase = reinterpret_cast(result->data.data()); const int componentIndex = isFeatureSupported(QRhi::RedOrAlpha8IsRed) ? 0 : 3; for (int y = 0; y < h; ++y) { const quint8 *src = srcBase + y * w * 4; quint8 *dst = dstBase + y * w; int count = w; while (count-- > 0) { *dst++ = src[componentIndex]; src += 4; } } } else { switch (result->format) { // For floating point formats try it because this can be // relevant for some use cases; if it works, then fine, if // not, there's nothing we can do. case QRhiTexture::RGBA16F: result->data.resize(w * h * 8); f->glReadPixels(0, 0, w, h, GL_RGBA, GL_HALF_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()); break; default: result->data.resize(w * h * 4); f->glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, result->data.data()); break; } } } else { result->data.resize(w * h * 4); result->data.fill('\0'); } if (fbo) { f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject()); f->glDeleteFramebuffers(1, &fbo); } if (result->completed) result->completed(); } break; case QGles2CommandBuffer::Command::SubImage: f->glBindTexture(cmd.args.subImage.target, cmd.args.subImage.texture); if (cmd.args.subImage.rowStartAlign != 4) f->glPixelStorei(GL_UNPACK_ALIGNMENT, cmd.args.subImage.rowStartAlign); if (cmd.args.subImage.rowLength != 0) f->glPixelStorei(GL_UNPACK_ROW_LENGTH, cmd.args.subImage.rowLength); if (cmd.args.subImage.target == GL_TEXTURE_3D) { f->glTexSubImage3D(cmd.args.subImage.target, cmd.args.subImage.level, cmd.args.subImage.dx, cmd.args.subImage.dy, cmd.args.subImage.dz, cmd.args.subImage.w, cmd.args.subImage.h, 1, 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, cmd.args.subImage.w, cmd.args.subImage.h, cmd.args.subImage.glformat, cmd.args.subImage.gltype, cmd.args.subImage.data); } if (cmd.args.subImage.rowStartAlign != 4) f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4); if (cmd.args.subImage.rowLength != 0) f->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); break; case QGles2CommandBuffer::Command::CompressedImage: f->glBindTexture(cmd.args.compressedImage.target, cmd.args.compressedImage.texture); if (cmd.args.compressedImage.target == GL_TEXTURE_3D) { f->glCompressedTexImage3D(cmd.args.compressedImage.target, cmd.args.compressedImage.level, 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 { f->glCompressedTexImage2D(cmd.args.compressedImage.faceTarget, cmd.args.compressedImage.level, cmd.args.compressedImage.glintformat, cmd.args.compressedImage.w, cmd.args.compressedImage.h, 0, cmd.args.compressedImage.size, cmd.args.compressedImage.data); } break; case QGles2CommandBuffer::Command::CompressedSubImage: f->glBindTexture(cmd.args.compressedSubImage.target, cmd.args.compressedSubImage.texture); if (cmd.args.compressedSubImage.target == GL_TEXTURE_3D) { f->glCompressedTexSubImage3D(cmd.args.compressedSubImage.target, cmd.args.compressedSubImage.level, cmd.args.compressedSubImage.dx, cmd.args.compressedSubImage.dy, cmd.args.compressedSubImage.dz, cmd.args.compressedSubImage.w, cmd.args.compressedSubImage.h, 1, 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, cmd.args.compressedSubImage.w, cmd.args.compressedSubImage.h, cmd.args.compressedSubImage.glintformat, cmd.args.compressedSubImage.size, cmd.args.compressedSubImage.data); } break; case QGles2CommandBuffer::Command::BlitFromRenderbuffer: { 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); f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]); f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.blitFromRb.target, cmd.args.blitFromRb.texture, cmd.args.blitFromRb.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->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject()); f->glDeleteFramebuffers(2, fbo); } break; case QGles2CommandBuffer::Command::GenMip: f->glBindTexture(cmd.args.genMip.target, cmd.args.genMip.texture); f->glGenerateMipmap(cmd.args.genMip.target); break; case QGles2CommandBuffer::Command::BindComputePipeline: { QGles2ComputePipeline *psD = QRHI_RES(QGles2ComputePipeline, cmd.args.bindComputePipeline.ps); f->glUseProgram(psD->program); } break; case QGles2CommandBuffer::Command::Dispatch: f->glDispatchCompute(cmd.args.dispatch.x, cmd.args.dispatch.y, cmd.args.dispatch.z); break; case QGles2CommandBuffer::Command::BarriersForPass: { if (!caps.compute) break; GLbitfield barriers = 0; QRhiPassResourceTracker &tracker(cbD->passResTrackers[cmd.args.barriersForPass.trackerIndex]); // we only care about after-write, not any other accesses, and // cannot tell if something was written in a shader several passes // ago: now the previously written resource may be used with an // access that was not in the previous passes, result in a missing // barrier in theory. Hence setting all barrier bits whenever // something previously written is used for the first time in a // subsequent pass. for (auto it = tracker.cbeginBuffers(), itEnd = tracker.cendBuffers(); it != itEnd; ++it) { QGles2Buffer::Access accessBeforePass = QGles2Buffer::Access(it->stateAtPassBegin.access); if (bufferAccessIsWrite(accessBeforePass)) barriers |= GL_ALL_BARRIER_BITS; } for (auto it = tracker.cbeginTextures(), itEnd = tracker.cendTextures(); it != itEnd; ++it) { QGles2Texture::Access accessBeforePass = QGles2Texture::Access(it->stateAtPassBegin.access); if (textureAccessIsWrite(accessBeforePass)) barriers |= GL_ALL_BARRIER_BITS; } if (barriers) f->glMemoryBarrier(barriers); } break; case QGles2CommandBuffer::Command::Barrier: if (caps.compute) f->glMemoryBarrier(cmd.args.barrier.barriers); break; default: break; } } if (state.instancedAttributesUsed) { for (int i = 0; i < CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT; ++i) { if (state.nonzeroAttribDivisor[i]) f->glVertexAttribDivisor(GLuint(i), 0); } for (int i = CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT; i <= state.maxUntrackedInstancedAttribute; ++i) f->glVertexAttribDivisor(GLuint(i), 0); } } void QRhiGles2::executeBindGraphicsPipeline(QGles2CommandBuffer *cbD, QGles2GraphicsPipeline *psD) { QGles2CommandBuffer::GraphicsPassState &state(cbD->graphicsPassState); const bool forceUpdate = !state.valid; state.valid = true; const bool scissor = psD->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor); if (forceUpdate || scissor != state.scissor) { state.scissor = scissor; if (scissor) f->glEnable(GL_SCISSOR_TEST); else f->glDisable(GL_SCISSOR_TEST); } const bool cullFace = psD->m_cullMode != QRhiGraphicsPipeline::None; const GLenum cullMode = cullFace ? toGlCullMode(psD->m_cullMode) : GL_NONE; if (forceUpdate || cullFace != state.cullFace || cullMode != state.cullMode) { state.cullFace = cullFace; state.cullMode = cullMode; if (cullFace) { f->glEnable(GL_CULL_FACE); f->glCullFace(cullMode); } else { f->glDisable(GL_CULL_FACE); } } const GLenum frontFace = toGlFrontFace(psD->m_frontFace); if (forceUpdate || frontFace != state.frontFace) { state.frontFace = frontFace; f->glFrontFace(frontFace); } 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 // nothing in Qt relies on it. However, considering that // glBlendFuncSeparatei is only available in GL 4.0+ and GLES 3.2+, we // may just live with this for now because no point in bothering if it // won't be usable on many GLES (3.1 or 3.0) systems. const QRhiGraphicsPipeline::TargetBlend &targetBlend(psD->m_targetBlends.first()); const QGles2CommandBuffer::GraphicsPassState::ColorMask colorMask = { targetBlend.colorWrite.testFlag(QRhiGraphicsPipeline::R), targetBlend.colorWrite.testFlag(QRhiGraphicsPipeline::G), targetBlend.colorWrite.testFlag(QRhiGraphicsPipeline::B), targetBlend.colorWrite.testFlag(QRhiGraphicsPipeline::A) }; if (forceUpdate || colorMask != state.colorMask) { state.colorMask = colorMask; f->glColorMask(colorMask.r, colorMask.g, colorMask.b, colorMask.a); } const bool blendEnabled = targetBlend.enable; const QGles2CommandBuffer::GraphicsPassState::Blend blend = { toGlBlendFactor(targetBlend.srcColor), toGlBlendFactor(targetBlend.dstColor), toGlBlendFactor(targetBlend.srcAlpha), toGlBlendFactor(targetBlend.dstAlpha), toGlBlendOp(targetBlend.opColor), toGlBlendOp(targetBlend.opAlpha) }; if (forceUpdate || blendEnabled != state.blendEnabled || (blendEnabled && blend != state.blend)) { state.blendEnabled = blendEnabled; if (blendEnabled) { state.blend = blend; f->glEnable(GL_BLEND); f->glBlendFuncSeparate(blend.srcColor, blend.dstColor, blend.srcAlpha, blend.dstAlpha); f->glBlendEquationSeparate(blend.opColor, blend.opAlpha); } else { f->glDisable(GL_BLEND); } } } else { const QGles2CommandBuffer::GraphicsPassState::ColorMask colorMask = { true, true, true, true }; if (forceUpdate || colorMask != state.colorMask) { state.colorMask = colorMask; f->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); } const bool blendEnabled = false; if (forceUpdate || blendEnabled != state.blendEnabled) { state.blendEnabled = blendEnabled; f->glDisable(GL_BLEND); } } const bool depthTest = psD->m_depthTest; if (forceUpdate || depthTest != state.depthTest) { state.depthTest = depthTest; if (depthTest) f->glEnable(GL_DEPTH_TEST); else f->glDisable(GL_DEPTH_TEST); } const bool depthWrite = psD->m_depthWrite; if (forceUpdate || depthWrite != state.depthWrite) { state.depthWrite = depthWrite; f->glDepthMask(depthWrite); } const GLenum depthFunc = toGlCompareOp(psD->m_depthOp); if (forceUpdate || depthFunc != state.depthFunc) { state.depthFunc = depthFunc; f->glDepthFunc(depthFunc); } const bool stencilTest = psD->m_stencilTest; const GLuint stencilReadMask = psD->m_stencilReadMask; const GLuint stencilWriteMask = psD->m_stencilWriteMask; const QGles2CommandBuffer::GraphicsPassState::StencilFace stencilFront = { toGlCompareOp(psD->m_stencilFront.compareOp), toGlStencilOp(psD->m_stencilFront.failOp), toGlStencilOp(psD->m_stencilFront.depthFailOp), toGlStencilOp(psD->m_stencilFront.passOp) }; const QGles2CommandBuffer::GraphicsPassState::StencilFace stencilBack = { toGlCompareOp(psD->m_stencilBack.compareOp), toGlStencilOp(psD->m_stencilBack.failOp), toGlStencilOp(psD->m_stencilBack.depthFailOp), toGlStencilOp(psD->m_stencilBack.passOp) }; if (forceUpdate || stencilTest != state.stencilTest || (stencilTest && (stencilReadMask != state.stencilReadMask || stencilWriteMask != state.stencilWriteMask || stencilFront != state.stencil[0] || stencilBack != state.stencil[1]))) { state.stencilTest = stencilTest; if (stencilTest) { state.stencilReadMask = stencilReadMask; state.stencilWriteMask = stencilWriteMask; state.stencil[0] = stencilFront; state.stencil[1] = stencilBack; f->glEnable(GL_STENCIL_TEST); f->glStencilFuncSeparate(GL_FRONT, stencilFront.func, state.dynamic.stencilRef, stencilReadMask); f->glStencilOpSeparate(GL_FRONT, stencilFront.failOp, stencilFront.zfailOp, stencilFront.zpassOp); f->glStencilMaskSeparate(GL_FRONT, stencilWriteMask); f->glStencilFuncSeparate(GL_BACK, stencilBack.func, state.dynamic.stencilRef, stencilReadMask); f->glStencilOpSeparate(GL_BACK, stencilBack.failOp, stencilBack.zfailOp, stencilBack.zpassOp); f->glStencilMaskSeparate(GL_BACK, stencilWriteMask); } else { f->glDisable(GL_STENCIL_TEST); } } const bool polyOffsetFill = psD->m_depthBias != 0 || !qFuzzyIsNull(psD->m_slopeScaledDepthBias); const float polyOffsetFactor = psD->m_slopeScaledDepthBias; const float polyOffsetUnits = psD->m_depthBias; if (forceUpdate || state.polyOffsetFill != polyOffsetFill || polyOffsetFactor != state.polyOffsetFactor || polyOffsetUnits != state.polyOffsetUnits) { state.polyOffsetFill = polyOffsetFill; state.polyOffsetFactor = polyOffsetFactor; state.polyOffsetUnits = polyOffsetUnits; if (polyOffsetFill) { f->glPolygonOffset(polyOffsetFactor, polyOffsetUnits); f->glEnable(GL_POLYGON_OFFSET_FILL); } else { f->glDisable(GL_POLYGON_OFFSET_FILL); } } if (psD->m_topology == QRhiGraphicsPipeline::Lines || psD->m_topology == QRhiGraphicsPipeline::LineStrip) { const float lineWidth = psD->m_lineWidth; if (forceUpdate || lineWidth != state.lineWidth) { state.lineWidth = lineWidth; f->glLineWidth(lineWidth); } } f->glUseProgram(psD->program); } static inline void qrhi_std140_to_packed(float *dst, int vecSize, int elemCount, const void *src) { const float *p = reinterpret_cast(src); for (int i = 0; i < elemCount; ++i) { for (int j = 0; j < vecSize; ++j) dst[vecSize * i + j] = *p++; p += 4 - vecSize; } } void QRhiGles2::bindShaderResources(QGles2CommandBuffer *cbD, QRhiGraphicsPipeline *maybeGraphicsPs, QRhiComputePipeline *maybeComputePs, QRhiShaderResourceBindings *srb, const uint *dynOfsPairs, int dynOfsCount) { 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 packedFloatArray; QGles2UniformDescriptionVector &uniforms(maybeGraphicsPs ? QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->uniforms : QRHI_RES(QGles2ComputePipeline, maybeComputePs)->uniforms); QGles2UniformState *uniformState = maybeGraphicsPs ? QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->uniformState : QRHI_RES(QGles2ComputePipeline, maybeComputePs)->uniformState; for (int i = 0, ie = srbD->m_bindings.count(); i != ie; ++i) { const QRhiShaderResourceBinding::Data *b = srbD->m_bindings.at(i).data(); switch (b->type) { case QRhiShaderResourceBinding::UniformBuffer: { int viewOffset = b->u.ubuf.offset; for (int j = 0; j < dynOfsCount; ++j) { if (dynOfsPairs[2 * j] == uint(b->binding)) { viewOffset = int(dynOfsPairs[2 * j + 1]); break; } } QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, b->u.ubuf.buf); const char *bufView = bufD->data.constData() + viewOffset; for (const QGles2UniformDescription &uniform : qAsConst(uniforms)) { if (uniform.binding == b->binding) { // in a uniform buffer everything is at least 4 byte aligned // so this should not cause unaligned reads const void *src = bufView + uniform.offset; #ifndef QT_NO_DEBUG if (uniform.arrayDim > 0 && uniform.type != QShaderDescription::Float && uniform.type != QShaderDescription::Vec2 && uniform.type != QShaderDescription::Vec3 && uniform.type != QShaderDescription::Vec4 && 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. " "Only the first element will be set.", uniform.binding, uniform.offset, uniform.type); } #endif // Our input is an std140 layout uniform block. See // "Standard Uniform Block Layout" in section 7.6.2.2 of // the OpenGL spec. This has some peculiar alignment // requirements, which is not what glUniform* wants. Hence // the unpacking/repacking for arrays and certain types. switch (uniform.type) { case QShaderDescription::Float: { const int elemCount = uniform.arrayDim; if (elemCount < 1) { const float v = *reinterpret_cast(src); if (uniform.glslLocation <= QGles2UniformState::MAX_TRACKED_LOCATION) { QGles2UniformState &thisUniformState(uniformState[uniform.glslLocation]); if (thisUniformState.componentCount != 1 || thisUniformState.v[0] != v) { thisUniformState.componentCount = 1; thisUniformState.v[0] = v; f->glUniform1f(uniform.glslLocation, v); } } else { f->glUniform1f(uniform.glslLocation, v); } } 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()); } } break; case QShaderDescription::Vec2: { const int elemCount = uniform.arrayDim; if (elemCount < 1) { const float *v = reinterpret_cast(src); if (uniform.glslLocation <= QGles2UniformState::MAX_TRACKED_LOCATION) { QGles2UniformState &thisUniformState(uniformState[uniform.glslLocation]); if (thisUniformState.componentCount != 2 || thisUniformState.v[0] != v[0] || thisUniformState.v[1] != v[1]) { thisUniformState.componentCount = 2; thisUniformState.v[0] = v[0]; thisUniformState.v[1] = v[1]; f->glUniform2fv(uniform.glslLocation, 1, v); } } else { 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()); } } break; case QShaderDescription::Vec3: { const int elemCount = uniform.arrayDim; if (elemCount < 1) { const float *v = reinterpret_cast(src); if (uniform.glslLocation <= QGles2UniformState::MAX_TRACKED_LOCATION) { QGles2UniformState &thisUniformState(uniformState[uniform.glslLocation]); if (thisUniformState.componentCount != 3 || thisUniformState.v[0] != v[0] || thisUniformState.v[1] != v[1] || thisUniformState.v[2] != v[2]) { thisUniformState.componentCount = 3; thisUniformState.v[0] = v[0]; thisUniformState.v[1] = v[1]; thisUniformState.v[2] = v[2]; f->glUniform3fv(uniform.glslLocation, 1, v); } } else { 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()); } } break; case QShaderDescription::Vec4: { const int elemCount = uniform.arrayDim; if (elemCount < 1) { const float *v = reinterpret_cast(src); if (uniform.glslLocation <= QGles2UniformState::MAX_TRACKED_LOCATION) { QGles2UniformState &thisUniformState(uniformState[uniform.glslLocation]); if (thisUniformState.componentCount != 4 || thisUniformState.v[0] != v[0] || thisUniformState.v[1] != v[1] || thisUniformState.v[2] != v[2] || thisUniformState.v[3] != v[3]) { thisUniformState.componentCount = 4; thisUniformState.v[0] = v[0]; thisUniformState.v[1] = v[1]; thisUniformState.v[2] = v[2]; thisUniformState.v[3] = v[3]; f->glUniform4fv(uniform.glslLocation, 1, v); } } else { f->glUniform4fv(uniform.glslLocation, 1, v); } } else { f->glUniform4fv(uniform.glslLocation, qMax(1, uniform.arrayDim), reinterpret_cast(src)); } } break; case QShaderDescription::Mat2: f->glUniformMatrix2fv(uniform.glslLocation, 1, GL_FALSE, reinterpret_cast(src)); break; case QShaderDescription::Mat3: { const int elemCount = uniform.arrayDim; if (elemCount < 1) { // 4 floats per column (or row, if row-major) float mat[9]; const float *srcMat = reinterpret_cast(src); memcpy(mat, srcMat, 3 * sizeof(float)); memcpy(mat + 3, srcMat + 4, 3 * sizeof(float)); 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()); } } break; case QShaderDescription::Mat4: f->glUniformMatrix4fv(uniform.glslLocation, qMax(1, uniform.arrayDim), GL_FALSE, reinterpret_cast(src)); break; case QShaderDescription::Int: f->glUniform1i(uniform.glslLocation, *reinterpret_cast(src)); break; case QShaderDescription::Int2: f->glUniform2iv(uniform.glslLocation, 1, reinterpret_cast(src)); break; case QShaderDescription::Int3: f->glUniform3iv(uniform.glslLocation, 1, reinterpret_cast(src)); break; case QShaderDescription::Int4: f->glUniform4iv(uniform.glslLocation, 1, reinterpret_cast(src)); break; case QShaderDescription::Uint: f->glUniform1ui(uniform.glslLocation, *reinterpret_cast(src)); break; case QShaderDescription::Uint2: f->glUniform2uiv(uniform.glslLocation, 1, reinterpret_cast(src)); break; case QShaderDescription::Uint3: f->glUniform3uiv(uniform.glslLocation, 1, reinterpret_cast(src)); break; case QShaderDescription::Uint4: f->glUniform4uiv(uniform.glslLocation, 1, reinterpret_cast(src)); break; case QShaderDescription::Bool: // a glsl bool is 4 bytes, like (u)int f->glUniform1i(uniform.glslLocation, *reinterpret_cast(src)); break; case QShaderDescription::Bool2: f->glUniform2iv(uniform.glslLocation, 1, reinterpret_cast(src)); break; case QShaderDescription::Bool3: f->glUniform3iv(uniform.glslLocation, 1, reinterpret_cast(src)); break; case QShaderDescription::Bool4: f->glUniform4iv(uniform.glslLocation, 1, reinterpret_cast(src)); break; default: qWarning("Uniform with buffer binding %d, buffer offset %d has unsupported type %d", uniform.binding, uniform.offset, uniform.type); break; } } } } break; case QRhiShaderResourceBinding::SampledTexture: { const QGles2SamplerDescriptionVector &samplers(maybeGraphicsPs ? QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->samplers : QRHI_RES(QGles2ComputePipeline, maybeComputePs)->samplers); void *ps; uint psGeneration; if (maybeGraphicsPs) { ps = maybeGraphicsPs; psGeneration = QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->generation; } else { ps = maybeComputePs; psGeneration = QRHI_RES(QGles2ComputePipeline, maybeComputePs)->generation; } for (int elem = 0; elem < b->u.stex.count; ++elem) { QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.stex.texSamplers[elem].tex); QGles2Sampler *samplerD = QRHI_RES(QGles2Sampler, b->u.stex.texSamplers[elem].sampler); for (const QGles2SamplerDescription &shaderSampler : samplers) { if (shaderSampler.binding == b->binding) { const bool samplerStateValid = texD->samplerState == samplerD->d; const bool cachedStateInRange = texUnit < 16; bool updateTextureBinding = true; if (samplerStateValid && cachedStateInRange) { // If we already encountered the same texture with // the same pipeline for this texture unit in the // current pass, then the shader program already // has the uniform set. As in a 3D scene one model // often has more than one associated texture map, // the savings here can become significant, // depending on the scene. if (cbD->textureUnitState[texUnit].ps == ps && cbD->textureUnitState[texUnit].psGeneration == psGeneration && cbD->textureUnitState[texUnit].texture == texD->texture) { updateTextureBinding = false; } } if (updateTextureBinding) { f->glActiveTexture(GL_TEXTURE0 + uint(texUnit)); activeTexUnitAltered = true; f->glBindTexture(texD->target, texD->texture); f->glUniform1i(shaderSampler.glslLocation + elem, texUnit); if (cachedStateInRange) { cbD->textureUnitState[texUnit].ps = ps; cbD->textureUnitState[texUnit].psGeneration = psGeneration; cbD->textureUnitState[texUnit].texture = texD->texture; } } ++texUnit; if (!samplerStateValid) { f->glTexParameteri(texD->target, GL_TEXTURE_MIN_FILTER, GLint(samplerD->d.glminfilter)); f->glTexParameteri(texD->target, GL_TEXTURE_MAG_FILTER, GLint(samplerD->d.glmagfilter)); f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_S, GLint(samplerD->d.glwraps)); f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_T, GLint(samplerD->d.glwrapt)); if (caps.texture3D) f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_R, GLint(samplerD->d.glwrapr)); if (caps.textureCompareMode) { if (samplerD->d.gltexcomparefunc != GL_NEVER) { f->glTexParameteri(texD->target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); f->glTexParameteri(texD->target, GL_TEXTURE_COMPARE_FUNC, GLint(samplerD->d.gltexcomparefunc)); } else { f->glTexParameteri(texD->target, GL_TEXTURE_COMPARE_MODE, GL_NONE); } } texD->samplerState = samplerD->d; } } } } } break; case QRhiShaderResourceBinding::ImageLoad: case QRhiShaderResourceBinding::ImageStore: case QRhiShaderResourceBinding::ImageLoadStore: { QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.simage.tex); const bool layered = texD->m_flags.testFlag(QRhiTexture::CubeMap); GLenum access = GL_READ_WRITE; if (b->type == QRhiShaderResourceBinding::ImageLoad) access = GL_READ_ONLY; else if (b->type == QRhiShaderResourceBinding::ImageStore) access = GL_WRITE_ONLY; f->glBindImageTexture(GLuint(b->binding), texD->texture, b->u.simage.level, layered, 0, access, texD->glsizedintformat); } break; case QRhiShaderResourceBinding::BufferLoad: case QRhiShaderResourceBinding::BufferStore: case QRhiShaderResourceBinding::BufferLoadStore: { QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, b->u.sbuf.buf); if (b->u.sbuf.offset == 0 && b->u.sbuf.maybeSize == 0) f->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, GLuint(b->binding), bufD->buffer); else f->glBindBufferRange(GL_SHADER_STORAGE_BUFFER, GLuint(b->binding), bufD->buffer, b->u.sbuf.offset, b->u.sbuf.maybeSize ? b->u.sbuf.maybeSize : bufD->m_size); } break; default: Q_UNREACHABLE(); break; } } if (activeTexUnitAltered) f->glActiveTexture(GL_TEXTURE0); } void QRhiGles2::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) { Q_ASSERT(QRHI_RES(QGles2CommandBuffer, cb)->recordingPass == QGles2CommandBuffer::NoPass); enqueueResourceUpdates(cb, resourceUpdates); } QGles2RenderTargetData *QRhiGles2::enqueueBindFramebuffer(QRhiRenderTarget *rt, QGles2CommandBuffer *cbD, bool *wantsColorClear, bool *wantsDsClear) { QGles2RenderTargetData *rtD = nullptr; QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]); QGles2CommandBuffer::Command &fbCmd(cbD->commands.get()); fbCmd.cmd = QGles2CommandBuffer::Command::BindFramebuffer; switch (rt->resourceType()) { case QRhiResource::RenderTarget: rtD = &QRHI_RES(QGles2ReferenceRenderTarget, rt)->d; if (wantsColorClear) *wantsColorClear = true; if (wantsDsClear) *wantsDsClear = true; fbCmd.args.bindFramebuffer.fbo = 0; fbCmd.args.bindFramebuffer.colorAttCount = 1; break; case QRhiResource::TextureRenderTarget: { QGles2TextureRenderTarget *rtTex = QRHI_RES(QGles2TextureRenderTarget, rt); rtD = &rtTex->d; if (wantsColorClear) *wantsColorClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents); if (wantsDsClear) *wantsDsClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents); fbCmd.args.bindFramebuffer.fbo = rtTex->framebuffer; fbCmd.args.bindFramebuffer.colorAttCount = rtD->colorAttCount; for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments(); it != itEnd; ++it) { const QRhiColorAttachment &colorAtt(*it); QGles2Texture *texD = QRHI_RES(QGles2Texture, colorAtt.texture()); QGles2Texture *resolveTexD = QRHI_RES(QGles2Texture, colorAtt.resolveTexture()); if (texD && cbD->passNeedsResourceTracking) { trackedRegisterTexture(&passResTracker, texD, QRhiPassResourceTracker::TexColorOutput, QRhiPassResourceTracker::TexColorOutputStage); } if (resolveTexD && cbD->passNeedsResourceTracking) { trackedRegisterTexture(&passResTracker, resolveTexD, QRhiPassResourceTracker::TexColorOutput, QRhiPassResourceTracker::TexColorOutputStage); } // renderbuffers cannot be written in shaders (no image store) so // they do not matter here } if (rtTex->m_desc.depthTexture() && cbD->passNeedsResourceTracking) { trackedRegisterTexture(&passResTracker, QRHI_RES(QGles2Texture, rtTex->m_desc.depthTexture()), QRhiPassResourceTracker::TexDepthOutput, QRhiPassResourceTracker::TexDepthOutputStage); } } break; default: Q_UNREACHABLE(); break; } fbCmd.args.bindFramebuffer.srgb = rtD->srgbUpdateAndBlend; return rtD; } void QRhiGles2::enqueueBarriersForPass(QGles2CommandBuffer *cbD) { cbD->passResTrackers.append(QRhiPassResourceTracker()); cbD->currentPassResTrackerIndex = cbD->passResTrackers.count() - 1; QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::BarriersForPass; cmd.args.barriersForPass.trackerIndex = cbD->currentPassResTrackerIndex; } void QRhiGles2::beginPass(QRhiCommandBuffer *cb, QRhiRenderTarget *rt, const QColor &colorClearValue, const QRhiDepthStencilClearValue &depthStencilClearValue, QRhiResourceUpdateBatch *resourceUpdates, QRhiCommandBuffer::BeginPassFlags flags) { QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::NoPass); if (resourceUpdates) enqueueResourceUpdates(cb, resourceUpdates); // Get a new resource tracker. Then add a command that will generate // glMemoryBarrier() calls based on that tracker when submitted. enqueueBarriersForPass(cbD); bool wantsColorClear, wantsDsClear; QGles2RenderTargetData *rtD = enqueueBindFramebuffer(rt, cbD, &wantsColorClear, &wantsDsClear); QGles2CommandBuffer::Command &clearCmd(cbD->commands.get()); clearCmd.cmd = QGles2CommandBuffer::Command::Clear; clearCmd.args.clear.mask = 0; if (rtD->colorAttCount && wantsColorClear) clearCmd.args.clear.mask |= GL_COLOR_BUFFER_BIT; if (rtD->dsAttCount && wantsDsClear) clearCmd.args.clear.mask |= GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; clearCmd.args.clear.c[0] = float(colorClearValue.redF()); clearCmd.args.clear.c[1] = float(colorClearValue.greenF()); clearCmd.args.clear.c[2] = float(colorClearValue.blueF()); clearCmd.args.clear.c[3] = float(colorClearValue.alphaF()); clearCmd.args.clear.d = depthStencilClearValue.depthClearValue(); clearCmd.args.clear.s = depthStencilClearValue.stencilClearValue(); cbD->recordingPass = QGles2CommandBuffer::RenderPass; cbD->passNeedsResourceTracking = !flags.testFlag(QRhiCommandBuffer::DoNotTrackResourcesForCompute); cbD->currentTarget = rt; cbD->resetCachedState(); } void QRhiGles2::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) { QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); 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()); 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()); const GLenum faceTargetBase = colorTexD->m_flags.testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : colorTexD->target; cmd.args.blitFromRb.target = faceTargetBase + uint(colorAtt.resolveLayer()); cmd.args.blitFromRb.texture = colorTexD->texture; cmd.args.blitFromRb.dstLevel = colorAtt.resolveLevel(); } } } cbD->recordingPass = QGles2CommandBuffer::NoPass; cbD->currentTarget = nullptr; if (resourceUpdates) enqueueResourceUpdates(cb, resourceUpdates); } void QRhiGles2::beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates, QRhiCommandBuffer::BeginPassFlags) { QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::NoPass); if (resourceUpdates) enqueueResourceUpdates(cb, resourceUpdates); enqueueBarriersForPass(cbD); cbD->recordingPass = QGles2CommandBuffer::ComputePass; cbD->resetCachedState(); } void QRhiGles2::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) { QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::ComputePass); cbD->recordingPass = QGles2CommandBuffer::NoPass; if (resourceUpdates) enqueueResourceUpdates(cb, resourceUpdates); } void QRhiGles2::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) { QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::ComputePass); QGles2ComputePipeline *psD = QRHI_RES(QGles2ComputePipeline, ps); const bool pipelineChanged = cbD->currentComputePipeline != ps || cbD->currentPipelineGeneration != psD->generation; if (pipelineChanged) { cbD->currentGraphicsPipeline = nullptr; cbD->currentComputePipeline = ps; cbD->currentPipelineGeneration = psD->generation; QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::BindComputePipeline; cmd.args.bindComputePipeline.ps = ps; } } template inline void qrhigl_accumulateComputeResource(T *writtenResources, QRhiResource *resource, QRhiShaderResourceBinding::Type bindingType, int loadTypeVal, int storeTypeVal, int loadStoreTypeVal) { int access = 0; if (bindingType == loadTypeVal) { access = QGles2CommandBuffer::ComputePassState::Read; } else { access = QGles2CommandBuffer::ComputePassState::Write; if (bindingType == loadStoreTypeVal) access |= QGles2CommandBuffer::ComputePassState::Read; } auto it = writtenResources->find(resource); if (it != writtenResources->end()) it->first |= access; else if (bindingType == storeTypeVal || bindingType == loadStoreTypeVal) writtenResources->insert(resource, { access, true }); } void QRhiGles2::dispatch(QRhiCommandBuffer *cb, int x, int y, int z) { QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::ComputePass); if (cbD->currentComputeSrb) { GLbitfield barriers = 0; // The key in the writtenResources map indicates that the resource was // written in a previous dispatch, whereas the value accumulates the // access mask in the current one. for (auto &accessAndIsNewFlag : cbD->computePassState.writtenResources) accessAndIsNewFlag = { 0, false }; QGles2ShaderResourceBindings *srbD = QRHI_RES(QGles2ShaderResourceBindings, cbD->currentComputeSrb); const int bindingCount = srbD->m_bindings.count(); for (int i = 0; i < bindingCount; ++i) { const QRhiShaderResourceBinding::Data *b = srbD->m_bindings.at(i).data(); switch (b->type) { case QRhiShaderResourceBinding::ImageLoad: case QRhiShaderResourceBinding::ImageStore: case QRhiShaderResourceBinding::ImageLoadStore: qrhigl_accumulateComputeResource(&cbD->computePassState.writtenResources, b->u.simage.tex, b->type, QRhiShaderResourceBinding::ImageLoad, QRhiShaderResourceBinding::ImageStore, QRhiShaderResourceBinding::ImageLoadStore); break; case QRhiShaderResourceBinding::BufferLoad: case QRhiShaderResourceBinding::BufferStore: case QRhiShaderResourceBinding::BufferLoadStore: qrhigl_accumulateComputeResource(&cbD->computePassState.writtenResources, b->u.sbuf.buf, b->type, QRhiShaderResourceBinding::BufferLoad, QRhiShaderResourceBinding::BufferStore, QRhiShaderResourceBinding::BufferLoadStore); break; default: break; } } for (auto it = cbD->computePassState.writtenResources.begin(); it != cbD->computePassState.writtenResources.end(); ) { const int accessInThisDispatch = it->first; const bool isNewInThisDispatch = it->second; if (accessInThisDispatch && !isNewInThisDispatch) { if (it.key()->resourceType() == QRhiResource::Texture) barriers |= GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; else barriers |= GL_SHADER_STORAGE_BARRIER_BIT; } // Anything that was previously written, but is only read now, can be // removed from the written list (because that previous write got a // corresponding barrier now). if (accessInThisDispatch == QGles2CommandBuffer::ComputePassState::Read) it = cbD->computePassState.writtenResources.erase(it); else ++it; } if (barriers) { QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::Barrier; cmd.args.barrier.barriers = barriers; } } QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::Dispatch; cmd.args.dispatch.x = GLuint(x); cmd.args.dispatch.y = GLuint(y); cmd.args.dispatch.z = GLuint(z); } static inline GLenum toGlShaderType(QRhiShaderStage::Type type) { switch (type) { case QRhiShaderStage::Vertex: return GL_VERTEX_SHADER; case QRhiShaderStage::Fragment: return GL_FRAGMENT_SHADER; case QRhiShaderStage::Compute: return GL_COMPUTE_SHADER; default: Q_UNREACHABLE(); return GL_VERTEX_SHADER; } } QByteArray QRhiGles2::shaderSource(const QRhiShaderStage &shaderStage, int *glslVersion) { const QShader bakedShader = shaderStage.shader(); QList versionsToTry; QByteArray source; if (caps.gles) { if (caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2)) { versionsToTry << 320 << 310 << 300 << 100; } else if (caps.ctxMajor == 3 && caps.ctxMinor == 1) { versionsToTry << 310 << 300 << 100; } else if (caps.ctxMajor == 3 && caps.ctxMinor == 0) { versionsToTry << 300 << 100; } else { versionsToTry << 100; } for (int v : versionsToTry) { QShaderVersion ver(v, QShaderVersion::GlslEs); source = bakedShader.shader({ QShader::GlslShader, ver, shaderStage.shaderVariant() }).shader(); if (!source.isEmpty()) { if (glslVersion) *glslVersion = v; break; } } } else { if (caps.ctxMajor > 4 || (caps.ctxMajor == 4 && caps.ctxMinor >= 6)) { versionsToTry << 460 << 450 << 440 << 430 << 420 << 410 << 400 << 330 << 150 << 140 << 130; } else if (caps.ctxMajor == 4 && caps.ctxMinor == 5) { versionsToTry << 450 << 440 << 430 << 420 << 410 << 400 << 330 << 150 << 140 << 130; } else if (caps.ctxMajor == 4 && caps.ctxMinor == 4) { versionsToTry << 440 << 430 << 420 << 410 << 400 << 330 << 150 << 140 << 130; } else if (caps.ctxMajor == 4 && caps.ctxMinor == 3) { versionsToTry << 430 << 420 << 410 << 400 << 330 << 150 << 140 << 130; } else if (caps.ctxMajor == 4 && caps.ctxMinor == 2) { versionsToTry << 420 << 410 << 400 << 330 << 150 << 140 << 130; } else if (caps.ctxMajor == 4 && caps.ctxMinor == 1) { versionsToTry << 410 << 400 << 330 << 150 << 140 << 130; } else if (caps.ctxMajor == 4 && caps.ctxMinor == 0) { versionsToTry << 400 << 330 << 150 << 140 << 130; } else if (caps.ctxMajor == 3 && caps.ctxMinor == 3) { versionsToTry << 330 << 150 << 140 << 130; } else if (caps.ctxMajor == 3 && caps.ctxMinor == 2) { versionsToTry << 150 << 140 << 130; } else if (caps.ctxMajor == 3 && caps.ctxMinor == 1) { versionsToTry << 140 << 130; } else if (caps.ctxMajor == 3 && caps.ctxMinor == 0) { versionsToTry << 130; } if (!caps.coreProfile) versionsToTry << 120; for (int v : versionsToTry) { source = bakedShader.shader({ QShader::GlslShader, v, shaderStage.shaderVariant() }).shader(); if (!source.isEmpty()) { if (glslVersion) *glslVersion = v; break; } } } if (source.isEmpty()) { qWarning() << "No GLSL shader code found (versions tried: " << versionsToTry << ") in baked shader" << bakedShader; } return source; } bool QRhiGles2::compileShader(GLuint program, const QRhiShaderStage &shaderStage, int *glslVersion) { const QByteArray source = shaderSource(shaderStage, glslVersion); if (source.isEmpty()) return false; GLuint shader; auto cacheIt = m_shaderCache.constFind(shaderStage); if (cacheIt != m_shaderCache.constEnd()) { shader = *cacheIt; } else { shader = f->glCreateShader(toGlShaderType(shaderStage.type())); const char *srcStr = source.constData(); const GLint srcLength = source.count(); f->glShaderSource(shader, 1, &srcStr, &srcLength); f->glCompileShader(shader); GLint compiled = 0; f->glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); if (!compiled) { GLint infoLogLength = 0; f->glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength); QByteArray log; if (infoLogLength > 1) { GLsizei length = 0; log.resize(infoLogLength); f->glGetShaderInfoLog(shader, infoLogLength, &length, log.data()); } qWarning("Failed to compile shader: %s\nSource was:\n%s", log.constData(), source.constData()); return false; } if (m_shaderCache.count() >= 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 m_shaderCache.clear(); } m_shaderCache.insert(shaderStage, shader); } f->glAttachShader(program, shader); return true; } bool QRhiGles2::linkProgram(GLuint program) { f->glLinkProgram(program); GLint linked = 0; f->glGetProgramiv(program, GL_LINK_STATUS, &linked); if (!linked) { GLint infoLogLength = 0; f->glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength); QByteArray log; if (infoLogLength > 1) { GLsizei length = 0; log.resize(infoLogLength); f->glGetProgramInfoLog(program, infoLogLength, &length, log.data()); } qWarning("Failed to link shader program: %s", log.constData()); return false; } return true; } void QRhiGles2::registerUniformIfActive(const QShaderDescription::BlockVariable &var, const QByteArray &namePrefix, int binding, int baseOffset, GLuint program, QSet *activeUniformLocations, QGles2UniformDescriptionVector *dst) { if (var.type == QShaderDescription::Struct) { qWarning("Nested structs are not supported at the moment. '%s' ignored.", var.name.constData()); return; } QGles2UniformDescription uniform; uniform.type = var.type; const QByteArray name = namePrefix + var.name; // Here we expect that the OpenGL implementation has proper active uniform // handling, meaning that a uniform that is declared but not accessed // elsewhere in the code is reported as -1 when querying the location. If // that is not the case, it won't break anything, but we'll generate // unnecessary glUniform* calls then. uniform.glslLocation = f->glGetUniformLocation(program, name.constData()); if (uniform.glslLocation >= 0 && !activeUniformLocations->contains(uniform.glslLocation)) { activeUniformLocations->insert(uniform.glslLocation); if (var.arrayDims.count() > 1) { qWarning("Array '%s' has more than one dimension. This is not supported.", var.name.constData()); return; } uniform.binding = binding; uniform.offset = uint(baseOffset + var.offset); uniform.size = var.size; uniform.arrayDim = var.arrayDims.isEmpty() ? 0 : var.arrayDims.first(); dst->append(uniform); } } void QRhiGles2::gatherUniforms(GLuint program, const QShaderDescription::UniformBlock &ub, QSet *activeUniformLocations, QGles2UniformDescriptionVector *dst) { QByteArray prefix = ub.structName + '.'; for (const QShaderDescription::BlockVariable &blockMember : ub.members) { if (blockMember.type == QShaderDescription::Struct) { QByteArray structPrefix = prefix + blockMember.name; const int baseOffset = blockMember.offset; if (blockMember.arrayDims.isEmpty()) { for (const QShaderDescription::BlockVariable &structMember : blockMember.structMembers) registerUniformIfActive(structMember, structPrefix + ".", ub.binding, baseOffset, program, activeUniformLocations, dst); } else { if (blockMember.arrayDims.count() > 1) { qWarning("Array of struct '%s' has more than one dimension. Only the first " "dimension is used.", blockMember.name.constData()); } const int dim = blockMember.arrayDims.first(); const int elemSize = blockMember.size / dim; int elemOffset = baseOffset; for (int di = 0; di < dim; ++di) { const QByteArray arrayPrefix = structPrefix + '[' + QByteArray::number(di) + ']' + '.'; for (const QShaderDescription::BlockVariable &structMember : blockMember.structMembers) registerUniformIfActive(structMember, arrayPrefix, ub.binding, elemOffset, program, activeUniformLocations, dst); elemOffset += elemSize; } } } else { registerUniformIfActive(blockMember, prefix, ub.binding, 0, program, activeUniformLocations, dst); } } } void QRhiGles2::gatherSamplers(GLuint program, const QShaderDescription::InOutVariable &v, QGles2SamplerDescriptionVector *dst) { QGles2SamplerDescription sampler; sampler.glslLocation = f->glGetUniformLocation(program, v.name.constData()); if (sampler.glslLocation >= 0) { sampler.binding = v.binding; dst->append(sampler); } } void QRhiGles2::sanityCheckVertexFragmentInterface(const QShaderDescription &vsDesc, const QShaderDescription &fsDesc) { if (!vsDesc.isValid() || !fsDesc.isValid()) return; // Print a warning if the fragment shader input for a given location uses a // name that does not match the vertex shader output at the same location. // This is not an error with any other API and not with GLSL >= 330 either, // but matters for older GLSL code that has no location qualifiers. for (const QShaderDescription::InOutVariable &outVar : vsDesc.outputVariables()) { for (const QShaderDescription::InOutVariable &inVar : fsDesc.inputVariables()) { if (inVar.location == outVar.location) { if (inVar.name != outVar.name) { qWarning("Vertex output name '%s' does not match fragment input '%s'. " "This should be avoided because it causes problems with older GLSL versions.", outVar.name.constData(), inVar.name.constData()); } break; } } } } bool QRhiGles2::isProgramBinaryDiskCacheEnabled() const { static QOpenGLProgramBinarySupportCheckWrapper checker; return checker.get(ctx)->isSupported(); } Q_GLOBAL_STATIC(QOpenGLProgramBinaryCache, qrhi_programBinaryCache); static inline QShader::Stage toShaderStage(QRhiShaderStage::Type type) { switch (type) { case QRhiShaderStage::Vertex: return QShader::VertexStage; case QRhiShaderStage::Fragment: return QShader::FragmentStage; case QRhiShaderStage::Compute: return QShader::ComputeStage; default: Q_UNREACHABLE(); return QShader::VertexStage; } } QRhiGles2::ProgramCacheResult QRhiGles2::tryLoadFromDiskOrPipelineCache(const QRhiShaderStage *stages, int stageCount, GLuint program, const QVector &inputVars, QByteArray *cacheKey) { Q_ASSERT(cacheKey); // the traditional QOpenGL disk cache since Qt 5.9 const bool legacyDiskCacheEnabled = isProgramBinaryDiskCacheEnabled(); // QRhi's own (set)PipelineCacheData() const bool pipelineCacheEnabled = caps.programBinary && !m_pipelineCache.isEmpty(); // calculating the cache key based on the source code is common for both types of caches if (legacyDiskCacheEnabled || pipelineCacheEnabled) { QOpenGLProgramBinaryCache::ProgramDesc binaryProgram; for (int i = 0; i < stageCount; ++i) { const QRhiShaderStage &stage(stages[i]); QByteArray source = shaderSource(stage, nullptr); if (source.isEmpty()) return QRhiGles2::ProgramCacheError; if (stage.type() == QRhiShaderStage::Vertex) { // Now add something to the key that indicates the vertex input locations. // A GLSL shader lower than 330 (150, 140, ...) will not have location // qualifiers. This means that the shader source code is the same // regardless of what locations inputVars contains. This becomes a problem // because we'll glBindAttribLocation the shader based on inputVars, but // that's only when compiling/linking when there was no cache hit. Picking // from the cache afterwards should take the input locations into account // since if inputVars has now different locations for the attributes, then // it is not ok to reuse a program binary that used different attribute // locations. For a lot of clients this would not be an issue since they // typically hardcode and use the same vertex locations on every run. Some // systems that dynamically generate shaders may end up with a non-stable // order (and so location numbers), however. This is sub-optimal because // it makes caching inefficient, and said clients should be fixed, but in // any case this should not break rendering. Hence including the locations // in the cache key. QMap inputLocations; // sorted by key when iterating for (const QShaderDescription::InOutVariable &var : inputVars) inputLocations.insert(var.name, var.location); source += QByteArrayLiteral("\n // "); // just to be nice; treated as an arbitrary string regardless for (auto it = inputLocations.cbegin(), end = inputLocations.cend(); it != end; ++it) { source += it.key(); source += QByteArray::number(it.value()); } source += QByteArrayLiteral("\n"); } binaryProgram.shaders.append(QOpenGLProgramBinaryCache::ShaderDesc(toShaderStage(stage.type()), source)); } *cacheKey = binaryProgram.cacheKey(); // Try our pipeline cache simulation first, if it got seeded with // setPipelineCacheData and there's a hit, then no need to go to the // filesystem at all. if (pipelineCacheEnabled) { auto it = m_pipelineCache.constFind(*cacheKey); if (it != m_pipelineCache.constEnd()) { GLenum err; for ( ; ; ) { err = f->glGetError(); if (err == GL_NO_ERROR || err == GL_CONTEXT_LOST) break; } f->glProgramBinary(program, it->format, it->data.constData(), it->data.size()); err = f->glGetError(); if (err == GL_NO_ERROR) { GLint linkStatus = 0; f->glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); if (linkStatus == GL_TRUE) return QRhiGles2::ProgramCacheHit; } } } if (legacyDiskCacheEnabled && qrhi_programBinaryCache()->load(*cacheKey, program)) { // use the logging category QOpenGLShaderProgram would qCDebug(lcOpenGLProgramDiskCache, "Program binary received from cache, program %u, key %s", program, cacheKey->constData()); return QRhiGles2::ProgramCacheHit; } } return QRhiGles2::ProgramCacheMiss; } void QRhiGles2::trySaveToDiskCache(GLuint program, const QByteArray &cacheKey) { // This is only for the traditional QOpenGL disk cache since Qt 5.9. if (isProgramBinaryDiskCacheEnabled()) { // use the logging category QOpenGLShaderProgram would qCDebug(lcOpenGLProgramDiskCache, "Saving program binary, program %u, key %s", program, cacheKey.constData()); qrhi_programBinaryCache()->save(cacheKey, program); } } void QRhiGles2::trySaveToPipelineCache(GLuint program, const QByteArray &cacheKey, bool force) { // This handles our own simulated "pipeline cache". (specific to QRhi, not // shared with legacy QOpenGL* stuff) if (caps.programBinary && (force || !m_pipelineCache.contains(cacheKey))) { GLint blobSize = 0; f->glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &blobSize); QByteArray blob(blobSize, Qt::Uninitialized); GLint outSize = 0; GLenum binaryFormat = 0; f->glGetProgramBinary(program, blobSize, &outSize, &binaryFormat, blob.data()); if (blobSize == outSize) m_pipelineCache.insert(cacheKey, { binaryFormat, blob }); } } QGles2Buffer::QGles2Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size) : QRhiBuffer(rhi, type, usage, size) { } QGles2Buffer::~QGles2Buffer() { destroy(); } void QGles2Buffer::destroy() { data.clear(); if (!buffer) return; QRhiGles2::DeferredReleaseEntry e; e.type = QRhiGles2::DeferredReleaseEntry::Buffer; e.buffer.buffer = buffer; buffer = 0; QRHI_RES_RHI(QRhiGles2); rhiD->releaseQueue.append(e); QRHI_PROF; QRHI_PROF_F(releaseBuffer(this)); rhiD->unregisterResource(this); } bool QGles2Buffer::create() { if (buffer) destroy(); QRHI_RES_RHI(QRhiGles2); QRHI_PROF; nonZeroSize = m_size <= 0 ? 256 : m_size; if (m_usage.testFlag(QRhiBuffer::UniformBuffer)) { if (int(m_usage) != QRhiBuffer::UniformBuffer) { qWarning("Uniform buffer: multiple usages specified, this is not supported by the OpenGL backend"); return false; } data.resize(nonZeroSize); QRHI_PROF_F(newBuffer(this, uint(nonZeroSize), 0, 1)); return true; } if (!rhiD->ensureContext()) return false; targetForDataOps = GL_ARRAY_BUFFER; if (m_usage.testFlag(QRhiBuffer::IndexBuffer)) targetForDataOps = GL_ELEMENT_ARRAY_BUFFER; else if (m_usage.testFlag(QRhiBuffer::StorageBuffer)) targetForDataOps = GL_SHADER_STORAGE_BUFFER; rhiD->f->glGenBuffers(1, &buffer); rhiD->f->glBindBuffer(targetForDataOps, buffer); rhiD->f->glBufferData(targetForDataOps, nonZeroSize, nullptr, m_type == Dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); usageState.access = AccessNone; QRHI_PROF_F(newBuffer(this, uint(nonZeroSize), 1, 0)); rhiD->registerResource(this); return true; } QRhiBuffer::NativeBuffer QGles2Buffer::nativeBuffer() { if (m_usage.testFlag(QRhiBuffer::UniformBuffer)) return { {}, 0 }; return { { &buffer }, 1 }; } char *QGles2Buffer::beginFullDynamicBufferUpdateForCurrentFrame() { Q_ASSERT(m_type == Dynamic); if (!m_usage.testFlag(UniformBuffer)) { QRHI_RES_RHI(QRhiGles2); rhiD->f->glBindBuffer(targetForDataOps, buffer); if (rhiD->caps.properMapBuffer) { return static_cast(rhiD->f->glMapBufferRange(targetForDataOps, 0, nonZeroSize, GL_MAP_READ_BIT | GL_MAP_WRITE_BIT)); } else { // Need some storage for the data, use the otherwise unused 'data' member. if (data.isEmpty()) data.resize(nonZeroSize); } } return data.data(); } void QGles2Buffer::endFullDynamicBufferUpdateForCurrentFrame() { if (!m_usage.testFlag(UniformBuffer)) { QRHI_RES_RHI(QRhiGles2); if (rhiD->caps.properMapBuffer) rhiD->f->glUnmapBuffer(targetForDataOps); else rhiD->f->glBufferSubData(targetForDataOps, 0, nonZeroSize, data.data()); } } QGles2RenderBuffer::QGles2RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, int sampleCount, QRhiRenderBuffer::Flags flags, QRhiTexture::Format backingFormatHint) : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags, backingFormatHint) { } QGles2RenderBuffer::~QGles2RenderBuffer() { destroy(); } void QGles2RenderBuffer::destroy() { if (!renderbuffer) return; QRhiGles2::DeferredReleaseEntry e; e.type = QRhiGles2::DeferredReleaseEntry::RenderBuffer; e.renderbuffer.renderbuffer = renderbuffer; e.renderbuffer.renderbuffer2 = stencilRenderbuffer; renderbuffer = 0; stencilRenderbuffer = 0; QRHI_RES_RHI(QRhiGles2); if (owns) rhiD->releaseQueue.append(e); QRHI_PROF; QRHI_PROF_F(releaseRenderBuffer(this)); rhiD->unregisterResource(this); } bool QGles2RenderBuffer::create() { if (renderbuffer) destroy(); QRHI_RES_RHI(QRhiGles2); QRHI_PROF; samples = rhiD->effectiveSampleCount(m_sampleCount); if (m_flags.testFlag(UsedWithSwapChainOnly)) { if (m_type == DepthStencil) { QRHI_PROF_F(newRenderBuffer(this, false, true, samples)); return true; } qWarning("RenderBuffer: UsedWithSwapChainOnly is meaningless in combination with Color"); } if (!rhiD->ensureContext()) return false; rhiD->f->glGenRenderbuffers(1, &renderbuffer); rhiD->f->glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize; switch (m_type) { case QRhiRenderBuffer::DepthStencil: if (rhiD->caps.msaaRenderBuffer && samples > 1) { rhiD->f->glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH24_STENCIL8, size.width(), size.height()); stencilRenderbuffer = 0; } else if (rhiD->caps.packedDepthStencil || rhiD->caps.needsDepthStencilCombinedAttach) { const GLenum storage = rhiD->caps.needsDepthStencilCombinedAttach ? GL_DEPTH_STENCIL : GL_DEPTH24_STENCIL8; rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, storage, size.width(), size.height()); stencilRenderbuffer = 0; } else { GLenum depthStorage = GL_DEPTH_COMPONENT; if (rhiD->caps.gles) { if (rhiD->caps.depth24) depthStorage = GL_DEPTH_COMPONENT24; else depthStorage = GL_DEPTH_COMPONENT16; // plain ES 2.0 only has this } const GLenum stencilStorage = rhiD->caps.gles ? GL_STENCIL_INDEX8 : GL_STENCIL_INDEX; rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, depthStorage, size.width(), size.height()); rhiD->f->glGenRenderbuffers(1, &stencilRenderbuffer); rhiD->f->glBindRenderbuffer(GL_RENDERBUFFER, stencilRenderbuffer); rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, stencilStorage, size.width(), size.height()); } QRHI_PROF_F(newRenderBuffer(this, false, false, samples)); break; case QRhiRenderBuffer::Color: { GLenum internalFormat = GL_RGBA4; // ES 2.0 if (rhiD->caps.rgba8Format) { internalFormat = GL_RGBA8; if (m_backingFormatHint != QRhiTexture::UnknownFormat) { GLenum glintformat, glformat, gltype; // only care about the sized internal format, the rest is not used here toGlTextureFormat(m_backingFormatHint, rhiD->caps, &glintformat, &internalFormat, &glformat, &gltype); } } if (rhiD->caps.msaaRenderBuffer && samples > 1) { rhiD->f->glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, internalFormat, size.width(), size.height()); } else { rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, internalFormat, size.width(), size.height()); } QRHI_PROF_F(newRenderBuffer(this, false, false, samples)); } break; default: Q_UNREACHABLE(); break; } owns = true; rhiD->registerResource(this); return true; } bool QGles2RenderBuffer::createFrom(NativeRenderBuffer src) { if (!src.object) return false; if (renderbuffer) destroy(); QRHI_RES_RHI(QRhiGles2); samples = rhiD->effectiveSampleCount(m_sampleCount); if (m_flags.testFlag(UsedWithSwapChainOnly)) qWarning("RenderBuffer: UsedWithSwapChainOnly is meaningless when importing an existing native object"); if (!rhiD->ensureContext()) return false; renderbuffer = src.object; QRHI_PROF; QRHI_PROF_F(newRenderBuffer(this, false, false, samples)); owns = false; rhiD->registerResource(this); return true; } QRhiTexture::Format QGles2RenderBuffer::backingFormat() const { if (m_backingFormatHint != QRhiTexture::UnknownFormat) return m_backingFormatHint; else return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat; } QGles2Texture::QGles2Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth, int sampleCount, Flags flags) : QRhiTexture(rhi, format, pixelSize, depth, sampleCount, flags) { } QGles2Texture::~QGles2Texture() { destroy(); } void QGles2Texture::destroy() { if (!texture) return; QRhiGles2::DeferredReleaseEntry e; e.type = QRhiGles2::DeferredReleaseEntry::Texture; e.texture.texture = texture; texture = 0; specified = false; zeroInitialized = false; QRHI_RES_RHI(QRhiGles2); if (owns) rhiD->releaseQueue.append(e); QRHI_PROF; QRHI_PROF_F(releaseTexture(this)); rhiD->unregisterResource(this); } bool QGles2Texture::prepareCreate(QSize *adjustedSize) { if (texture) destroy(); QRHI_RES_RHI(QRhiGles2); 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 is3D = m_flags.testFlag(ThreeDimensional); const bool hasMipMaps = m_flags.testFlag(MipMapped); const bool isCompressed = rhiD->isCompressedFormat(m_format); if (is3D && !rhiD->caps.texture3D) { qWarning("3D textures are not supported"); return false; } if (isCube && is3D) { qWarning("Texture cannot be both cube and 3D"); return false; } m_depth = qMax(1, m_depth); if (m_depth > 1 && !is3D) { qWarning("Texture cannot have a depth of %d when it is not 3D", m_depth); return false; } target = isCube ? GL_TEXTURE_CUBE_MAP : m_sampleCount > 1 ? GL_TEXTURE_2D_MULTISAMPLE : (is3D ? GL_TEXTURE_3D : GL_TEXTURE_2D); if (m_flags.testFlag(ExternalOES)) target = GL_TEXTURE_EXTERNAL_OES; mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1; gltype = GL_UNSIGNED_BYTE; if (isCompressed) { if (m_flags.testFlag(UsedWithLoadStore)) { qWarning("Compressed texture cannot be used with image load/store"); return false; } glintformat = toGlCompressedTextureFormat(m_format, m_flags); if (!glintformat) { qWarning("Compressed format %d not mappable to GL compressed format", m_format); return false; } glsizedintformat = glintformat; glformat = GL_RGBA; } else { toGlTextureFormat(m_format, rhiD->caps, &glintformat, &glsizedintformat, &glformat, &gltype); } samplerState = QGles2SamplerData(); usageState.access = AccessNone; if (adjustedSize) *adjustedSize = size; return true; } bool QGles2Texture::create() { QSize size; if (!prepareCreate(&size)) return false; QRHI_RES_RHI(QRhiGles2); rhiD->f->glGenTextures(1, &texture); const bool isCube = m_flags.testFlag(CubeMap); const bool is3D = m_flags.testFlag(ThreeDimensional); const bool hasMipMaps = m_flags.testFlag(MipMapped); const bool isCompressed = rhiD->isCompressedFormat(m_format); if (!isCompressed) { rhiD->f->glBindTexture(target, texture); if (!m_flags.testFlag(UsedWithLoadStore)) { if (is3D) { if (hasMipMaps) { for (int level = 0; level != mipLevelCount; ++level) { const QSize mipSize = rhiD->q->sizeForMipLevel(level, size); rhiD->f->glTexImage3D(target, level, GLint(glintformat), mipSize.width(), mipSize.height(), m_depth, 0, glformat, gltype, nullptr); } } else { rhiD->f->glTexImage3D(target, 0, GLint(glintformat), size.width(), size.height(), m_depth, 0, glformat, gltype, nullptr); } } else if (hasMipMaps || isCube) { const GLenum faceTargetBase = isCube ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : target; for (int layer = 0, layerCount = isCube ? 6 : 1; layer != layerCount; ++layer) { for (int level = 0; level != mipLevelCount; ++level) { const QSize mipSize = rhiD->q->sizeForMipLevel(level, size); rhiD->f->glTexImage2D(faceTargetBase + uint(layer), level, GLint(glintformat), mipSize.width(), mipSize.height(), 0, glformat, gltype, nullptr); } } } 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) rhiD->f->glTexStorage3D(target, mipLevelCount, glsizedintformat, size.width(), size.height(), m_depth); else rhiD->f->glTexStorage2D(target, mipLevelCount, glsizedintformat, size.width(), size.height()); } specified = true; } else { // Cannot use glCompressedTexImage2D without valid data, so defer. // Compressed textures will not be used as render targets so this is // not an issue. specified = false; } QRHI_PROF; QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, 1)); owns = true; generation += 1; rhiD->registerResource(this); return true; } bool QGles2Texture::createFrom(QRhiTexture::NativeTexture src) { const uint textureId = uint(src.object); if (textureId == 0) return false; if (!prepareCreate()) return false; texture = textureId; specified = true; zeroInitialized = true; QRHI_RES_RHI(QRhiGles2); QRHI_PROF; QRHI_PROF_F(newTexture(this, false, mipLevelCount, m_flags.testFlag(CubeMap) ? 6 : 1, 1)); owns = false; generation += 1; rhiD->registerResource(this); return true; } QRhiTexture::NativeTexture QGles2Texture::nativeTexture() { return {texture, 0}; } QGles2Sampler::QGles2Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, AddressMode u, AddressMode v, AddressMode w) : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v, w) { } QGles2Sampler::~QGles2Sampler() { destroy(); } void QGles2Sampler::destroy() { // nothing to do here } bool QGles2Sampler::create() { d.glminfilter = toGlMinFilter(m_minFilter, m_mipmapMode); d.glmagfilter = toGlMagFilter(m_magFilter); d.glwraps = toGlWrapMode(m_addressU); d.glwrapt = toGlWrapMode(m_addressV); d.glwrapr = toGlWrapMode(m_addressW); d.gltexcomparefunc = toGlTextureCompareFunc(m_compareOp); generation += 1; return true; } // dummy, no Vulkan-style RenderPass+Framebuffer concept here QGles2RenderPassDescriptor::QGles2RenderPassDescriptor(QRhiImplementation *rhi) : QRhiRenderPassDescriptor(rhi) { } QGles2RenderPassDescriptor::~QGles2RenderPassDescriptor() { destroy(); } void QGles2RenderPassDescriptor::destroy() { // nothing to do here } bool QGles2RenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const { Q_UNUSED(other); return true; } QRhiRenderPassDescriptor *QGles2RenderPassDescriptor::newCompatibleRenderPassDescriptor() const { return new QGles2RenderPassDescriptor(m_rhi); } QGles2ReferenceRenderTarget::QGles2ReferenceRenderTarget(QRhiImplementation *rhi) : QRhiRenderTarget(rhi), d(rhi) { } QGles2ReferenceRenderTarget::~QGles2ReferenceRenderTarget() { destroy(); } void QGles2ReferenceRenderTarget::destroy() { // nothing to do here } QSize QGles2ReferenceRenderTarget::pixelSize() const { return d.pixelSize; } float QGles2ReferenceRenderTarget::devicePixelRatio() const { return d.dpr; } int QGles2ReferenceRenderTarget::sampleCount() const { return d.sampleCount; } QGles2TextureRenderTarget::QGles2TextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags) : QRhiTextureRenderTarget(rhi, desc, flags), d(rhi) { } QGles2TextureRenderTarget::~QGles2TextureRenderTarget() { destroy(); } void QGles2TextureRenderTarget::destroy() { if (!framebuffer) return; QRhiGles2::DeferredReleaseEntry e; e.type = QRhiGles2::DeferredReleaseEntry::TextureRenderTarget; e.textureRenderTarget.framebuffer = framebuffer; framebuffer = 0; QRHI_RES_RHI(QRhiGles2); rhiD->releaseQueue.append(e); rhiD->unregisterResource(this); } QRhiRenderPassDescriptor *QGles2TextureRenderTarget::newCompatibleRenderPassDescriptor() { return new QGles2RenderPassDescriptor(m_rhi); } bool QGles2TextureRenderTarget::create() { QRHI_RES_RHI(QRhiGles2); if (framebuffer) destroy(); const bool hasColorAttachments = m_desc.cbeginColorAttachments() != m_desc.cendColorAttachments(); 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(); if (count > rhiD->caps.maxDrawBuffers) { qWarning("QGles2TextureRenderTarget: Too many color attachments (%d, max is %d)", count, rhiD->caps.maxDrawBuffers); } } if (m_desc.depthTexture() && !rhiD->caps.depthTexture) qWarning("QGles2TextureRenderTarget: Depth texture is not supported and will be ignored"); if (!rhiD->ensureContext()) return false; rhiD->f->glGenFramebuffers(1, &framebuffer); rhiD->f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); d.colorAttCount = 0; int attIndex = 0; for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) { d.colorAttCount += 1; const QRhiColorAttachment &colorAtt(*it); QRhiTexture *texture = colorAtt.texture(); QRhiRenderBuffer *renderBuffer = colorAtt.renderBuffer(); Q_ASSERT(texture || renderBuffer); if (texture) { QGles2Texture *texD = QRHI_RES(QGles2Texture, texture); Q_ASSERT(texD->texture && texD->specified); if (texD->flags().testFlag(QRhiTexture::ThreeDimensional)) { rhiD->f->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), texD->texture, colorAtt.level(), colorAtt.layer()); } 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; } } else if (renderBuffer) { QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, renderBuffer); rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), GL_RENDERBUFFER, rbD->renderbuffer); if (attIndex == 0) { d.pixelSize = rbD->pixelSize(); d.sampleCount = rbD->samples; } } } if (hasDepthStencil) { if (m_desc.depthStencilBuffer()) { QGles2RenderBuffer *depthRbD = QRHI_RES(QGles2RenderBuffer, m_desc.depthStencilBuffer()); if (rhiD->caps.needsDepthStencilCombinedAttach) { rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthRbD->renderbuffer); } else { rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRbD->renderbuffer); if (depthRbD->stencilRenderbuffer) rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthRbD->stencilRenderbuffer); else // packed rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthRbD->renderbuffer); } if (d.colorAttCount == 0) { d.pixelSize = depthRbD->pixelSize(); d.sampleCount = depthRbD->samples; } } else { QGles2Texture *depthTexD = QRHI_RES(QGles2Texture, m_desc.depthTexture()); rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexD->target, depthTexD->texture, 0); if (d.colorAttCount == 0) { d.pixelSize = depthTexD->pixelSize(); d.sampleCount = 1; } } d.dsAttCount = 1; } else { d.dsAttCount = 0; } d.dpr = 1; d.rp = QRHI_RES(QGles2RenderPassDescriptor, m_renderPassDesc); GLenum status = rhiD->f->glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_NO_ERROR && status != GL_FRAMEBUFFER_COMPLETE) { qWarning("Framebuffer incomplete: 0x%x", status); return false; } rhiD->registerResource(this); return true; } QSize QGles2TextureRenderTarget::pixelSize() const { return d.pixelSize; } float QGles2TextureRenderTarget::devicePixelRatio() const { return d.dpr; } int QGles2TextureRenderTarget::sampleCount() const { return d.sampleCount; } QGles2ShaderResourceBindings::QGles2ShaderResourceBindings(QRhiImplementation *rhi) : QRhiShaderResourceBindings(rhi) { } QGles2ShaderResourceBindings::~QGles2ShaderResourceBindings() { destroy(); } void QGles2ShaderResourceBindings::destroy() { // nothing to do here } bool QGles2ShaderResourceBindings::create() { QRHI_RES_RHI(QRhiGles2); if (!rhiD->sanityCheckShaderResourceBindings(this)) return false; hasDynamicOffset = false; for (int i = 0, ie = m_bindings.count(); i != ie; ++i) { const QRhiShaderResourceBinding::Data *b = m_bindings.at(i).data(); if (b->type == QRhiShaderResourceBinding::UniformBuffer) { if (b->u.ubuf.hasDynamicOffset) { hasDynamicOffset = true; break; } } } rhiD->updateLayoutDesc(this); generation += 1; return true; } QGles2GraphicsPipeline::QGles2GraphicsPipeline(QRhiImplementation *rhi) : QRhiGraphicsPipeline(rhi) { } QGles2GraphicsPipeline::~QGles2GraphicsPipeline() { destroy(); } void QGles2GraphicsPipeline::destroy() { if (!program) return; QRhiGles2::DeferredReleaseEntry e; e.type = QRhiGles2::DeferredReleaseEntry::Pipeline; e.pipeline.program = program; program = 0; uniforms.clear(); samplers.clear(); QRHI_RES_RHI(QRhiGles2); rhiD->releaseQueue.append(e); rhiD->unregisterResource(this); } bool QGles2GraphicsPipeline::create() { QRHI_RES_RHI(QRhiGles2); if (program) destroy(); if (!rhiD->ensureContext()) return false; if (!rhiD->sanityCheckGraphicsPipeline(this)) return false; drawMode = toGlTopology(m_topology); program = rhiD->f->glCreateProgram(); QShaderDescription vsDesc; QShaderDescription fsDesc; for (const QRhiShaderStage &shaderStage : qAsConst(m_shaderStages)) { if (shaderStage.type() == QRhiShaderStage::Vertex) vsDesc = shaderStage.shader().description(); else if (shaderStage.type() == QRhiShaderStage::Fragment) fsDesc = shaderStage.shader().description(); } QByteArray cacheKey; QRhiGles2::ProgramCacheResult cacheResult = rhiD->tryLoadFromDiskOrPipelineCache(m_shaderStages.constData(), m_shaderStages.count(), program, vsDesc.inputVariables(), &cacheKey); if (cacheResult == QRhiGles2::ProgramCacheError) return false; if (cacheResult == QRhiGles2::ProgramCacheMiss) { for (const QRhiShaderStage &shaderStage : qAsConst(m_shaderStages)) { if (shaderStage.type() == QRhiShaderStage::Vertex) { if (!rhiD->compileShader(program, shaderStage, nullptr)) return false; } else if (shaderStage.type() == QRhiShaderStage::Fragment) { if (!rhiD->compileShader(program, shaderStage, nullptr)) return false; } } // important when GLSL <= 150 is used that does not have location qualifiers for (const QShaderDescription::InOutVariable &inVar : vsDesc.inputVariables()) rhiD->f->glBindAttribLocation(program, GLuint(inVar.location), inVar.name); rhiD->sanityCheckVertexFragmentInterface(vsDesc, fsDesc); if (!rhiD->linkProgram(program)) return false; if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave)) { // force replacing existing cache entry (if there is one, then // something is wrong with it, as there was no hit) rhiD->trySaveToPipelineCache(program, cacheKey, true); } else { // legacy QOpenGLShaderProgram style behavior: the "pipeline cache" // was not enabled, so instead store to the Qt 5 disk cache rhiD->trySaveToDiskCache(program, cacheKey); } } else { Q_ASSERT(cacheResult == QRhiGles2::ProgramCacheHit); if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave)) { // just so that it ends up in the pipeline cache also when the hit was // from the disk cache rhiD->trySaveToPipelineCache(program, cacheKey); } } // Use the same work area for the vertex & fragment stages, thus ensuring // that we will not do superfluous glUniform calls for uniforms that are // present in both shaders. QSet activeUniformLocations; for (const QShaderDescription::UniformBlock &ub : vsDesc.uniformBlocks()) rhiD->gatherUniforms(program, ub, &activeUniformLocations, &uniforms); for (const QShaderDescription::UniformBlock &ub : fsDesc.uniformBlocks()) rhiD->gatherUniforms(program, ub, &activeUniformLocations, &uniforms); std::sort(uniforms.begin(), uniforms.end(), [](const QGles2UniformDescription &a, const QGles2UniformDescription &b) { return a.offset < b.offset; }); for (const QShaderDescription::InOutVariable &v : vsDesc.combinedImageSamplers()) rhiD->gatherSamplers(program, v, &samplers); for (const QShaderDescription::InOutVariable &v : fsDesc.combinedImageSamplers()) rhiD->gatherSamplers(program, v, &samplers); memset(uniformState, 0, sizeof(uniformState)); currentSrb = nullptr; currentSrbGeneration = 0; generation += 1; rhiD->registerResource(this); return true; } QGles2ComputePipeline::QGles2ComputePipeline(QRhiImplementation *rhi) : QRhiComputePipeline(rhi) { } QGles2ComputePipeline::~QGles2ComputePipeline() { destroy(); } void QGles2ComputePipeline::destroy() { if (!program) return; QRhiGles2::DeferredReleaseEntry e; e.type = QRhiGles2::DeferredReleaseEntry::Pipeline; e.pipeline.program = program; program = 0; uniforms.clear(); samplers.clear(); QRHI_RES_RHI(QRhiGles2); rhiD->releaseQueue.append(e); rhiD->unregisterResource(this); } bool QGles2ComputePipeline::create() { QRHI_RES_RHI(QRhiGles2); if (program) destroy(); if (!rhiD->ensureContext()) return false; const QShaderDescription csDesc = m_shaderStage.shader().description(); program = rhiD->f->glCreateProgram(); QByteArray cacheKey; QRhiGles2::ProgramCacheResult cacheResult = rhiD->tryLoadFromDiskOrPipelineCache(&m_shaderStage, 1, program, {}, &cacheKey); if (cacheResult == QRhiGles2::ProgramCacheError) return false; if (cacheResult == QRhiGles2::ProgramCacheMiss) { if (!rhiD->compileShader(program, m_shaderStage, nullptr)) return false; if (!rhiD->linkProgram(program)) return false; if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave)) { // force replacing existing cache entry (if there is one, then // something is wrong with it, as there was no hit) rhiD->trySaveToPipelineCache(program, cacheKey, true); } else { // legacy QOpenGLShaderProgram style behavior: the "pipeline cache" // was not enabled, so instead store to the Qt 5 disk cache rhiD->trySaveToDiskCache(program, cacheKey); } } else { Q_ASSERT(cacheResult == QRhiGles2::ProgramCacheHit); if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave)) { // just so that it ends up in the pipeline cache also when the hit was // from the disk cache rhiD->trySaveToPipelineCache(program, cacheKey); } } QSet activeUniformLocations; for (const QShaderDescription::UniformBlock &ub : csDesc.uniformBlocks()) rhiD->gatherUniforms(program, ub, &activeUniformLocations, &uniforms); for (const QShaderDescription::InOutVariable &v : csDesc.combinedImageSamplers()) rhiD->gatherSamplers(program, v, &samplers); // storage images and buffers need no special steps here memset(uniformState, 0, sizeof(uniformState)); currentSrb = nullptr; currentSrbGeneration = 0; generation += 1; rhiD->registerResource(this); return true; } QGles2CommandBuffer::QGles2CommandBuffer(QRhiImplementation *rhi) : QRhiCommandBuffer(rhi) { resetState(); } QGles2CommandBuffer::~QGles2CommandBuffer() { destroy(); } void QGles2CommandBuffer::destroy() { // nothing to do here } QGles2SwapChain::QGles2SwapChain(QRhiImplementation *rhi) : QRhiSwapChain(rhi), rt(rhi), cb(rhi) { } QGles2SwapChain::~QGles2SwapChain() { destroy(); } void QGles2SwapChain::destroy() { QRHI_PROF; QRHI_PROF_F(releaseSwapChain(this)); } QRhiCommandBuffer *QGles2SwapChain::currentFrameCommandBuffer() { return &cb; } QRhiRenderTarget *QGles2SwapChain::currentFrameRenderTarget() { return &rt; } QSize QGles2SwapChain::surfacePixelSize() { Q_ASSERT(m_window); return m_window->size() * m_window->devicePixelRatio(); } QRhiRenderPassDescriptor *QGles2SwapChain::newCompatibleRenderPassDescriptor() { return new QGles2RenderPassDescriptor(m_rhi); } bool QGles2SwapChain::createOrResize() { surface = m_window; m_currentPixelSize = surfacePixelSize(); pixelSize = m_currentPixelSize; if (m_depthStencil && m_depthStencil->flags().testFlag(QRhiRenderBuffer::UsedWithSwapChainOnly) && m_depthStencil->pixelSize() != pixelSize) { m_depthStencil->setPixelSize(pixelSize); 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); frameCount = 0; QRHI_PROF; // make something up QRHI_PROF_F(resizeSwapChain(this, 2, m_sampleCount > 1 ? 2 : 0, m_sampleCount)); return true; } QT_END_NAMESPACE