// Copyright (C) 2017 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include #include #include #include #include #include #include "qopenglcompositorbackingstore_p.h" #include "qopenglcompositor_p.h" #ifndef GL_UNPACK_ROW_LENGTH #define GL_UNPACK_ROW_LENGTH 0x0CF2 #endif QT_BEGIN_NAMESPACE /*! \class QOpenGLCompositorBackingStore \brief A backing store implementation for OpenGL \since 5.4 \internal \ingroup qpa This implementation uploads raster-rendered widget windows into textures. It is meant to be used with QOpenGLCompositor that composites the textures onto a single native window using OpenGL. This means that multiple top-level widgets are supported without creating actual native windows for each of them. \note It is important to call notifyComposited() from the corresponding platform window's endCompositing() callback (inherited from QOpenGLCompositorWindow). \note When implementing QOpenGLCompositorWindow::textures() for windows of type RasterSurface or RasterGLSurface, simply return the list provided by this class' textures(). */ QOpenGLCompositorBackingStore::QOpenGLCompositorBackingStore(QWindow *window) : QPlatformBackingStore(window), m_window(window), m_bsTexture(0), m_bsTextureWrapper(nullptr), m_bsTextureContext(0), m_textures(new QPlatformTextureList), m_lockedWidgetTextures(0), m_rhi(nullptr) { } QOpenGLCompositorBackingStore::~QOpenGLCompositorBackingStore() { if (m_bsTexture && m_rhi) { delete m_bsTextureWrapper; // Contexts are sharing resources, won't matter which one is // current here, use the rhi's shortcut. m_rhi->makeThreadLocalNativeContextCurrent(); glDeleteTextures(1, &m_bsTexture); } delete m_textures; // this does not actually own any GL resources } QPaintDevice *QOpenGLCompositorBackingStore::paintDevice() { return &m_image; } void QOpenGLCompositorBackingStore::updateTexture() { if (!m_bsTexture) { m_bsTextureContext = QOpenGLContext::currentContext(); Q_ASSERT(m_bsTextureContext); glGenTextures(1, &m_bsTexture); glBindTexture(GL_TEXTURE_2D, m_bsTexture); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_image.width(), m_image.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); } else { glBindTexture(GL_TEXTURE_2D, m_bsTexture); } if (!m_dirty.isNull()) { QRegion fixed; QRect imageRect = m_image.rect(); QOpenGLContext *ctx = QOpenGLContext::currentContext(); if (!ctx->isOpenGLES() || ctx->format().majorVersion() >= 3) { for (const QRect &rect : m_dirty) { QRect r = imageRect & rect; glPixelStorei(GL_UNPACK_ROW_LENGTH, m_image.width()); glTexSubImage2D(GL_TEXTURE_2D, 0, r.x(), r.y(), r.width(), r.height(), GL_RGBA, GL_UNSIGNED_BYTE, m_image.constScanLine(r.y()) + r.x() * 4); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); } } else { for (const QRect &rect : m_dirty) { // intersect with image rect to be sure QRect r = imageRect & rect; // if the rect is wide enough it's cheaper to just // extend it instead of doing an image copy if (r.width() >= imageRect.width() / 2) { r.setX(0); r.setWidth(imageRect.width()); } fixed |= r; } for (const QRect &rect : fixed) { // if the sub-rect is full-width we can pass the image data directly to // OpenGL instead of copying, since there's no gap between scanlines if (rect.width() == imageRect.width()) { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, rect.y(), rect.width(), rect.height(), GL_RGBA, GL_UNSIGNED_BYTE, m_image.constScanLine(rect.y())); } else { glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x(), rect.y(), rect.width(), rect.height(), GL_RGBA, GL_UNSIGNED_BYTE, m_image.copy(rect).constBits()); } } } m_dirty = QRegion(); } if (!m_bsTextureWrapper) { m_bsTextureWrapper = m_rhi->newTexture(QRhiTexture::RGBA8, m_image.size()); m_bsTextureWrapper->createFrom({m_bsTexture, 0}); } } void QOpenGLCompositorBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) { // Called for ordinary raster windows. auto *handle = dynamic_cast(window->handle()); if (handle && !handle->backingStore()) handle->setBackingStore(this); Q_UNUSED(region); Q_UNUSED(offset); m_rhi = rhi(); if (!m_rhi) { setRhiConfig(QPlatformBackingStoreRhiConfig(QPlatformBackingStoreRhiConfig::OpenGL)); m_rhi = rhi(); } Q_ASSERT(m_rhi); QOpenGLCompositor *compositor = QOpenGLCompositor::instance(); QOpenGLContext *dstCtx = compositor->context(); if (!dstCtx) return; QWindow *dstWin = compositor->targetWindow(); if (!dstWin) return; if (!dstCtx->makeCurrent(dstWin)) return; updateTexture(); m_textures->clear(); m_textures->appendTexture(nullptr, m_bsTextureWrapper, window->geometry()); compositor->update(); } QPlatformBackingStore::FlushResult QOpenGLCompositorBackingStore::rhiFlush(QWindow *window, qreal sourceDevicePixelRatio, const QRegion ®ion, const QPoint &offset, QPlatformTextureList *textures, bool translucentBackground) { // QOpenGLWidget/QQuickWidget content provided as textures. The raster content goes on top. Q_UNUSED(region); Q_UNUSED(offset); Q_UNUSED(translucentBackground); Q_UNUSED(sourceDevicePixelRatio); m_rhi = rhi(); if (!m_rhi) { setRhiConfig(QPlatformBackingStoreRhiConfig(QPlatformBackingStoreRhiConfig::OpenGL)); m_rhi = rhi(); } Q_ASSERT(m_rhi); QOpenGLCompositor *compositor = QOpenGLCompositor::instance(); QOpenGLContext *dstCtx = compositor->context(); if (!dstCtx) return FlushFailed; QWindow *dstWin = compositor->targetWindow(); if (!dstWin) return FlushFailed; if (!dstCtx->makeCurrent(dstWin)) return FlushFailed; QWindowPrivate::get(window)->lastComposeTime.start(); m_textures->clear(); for (int i = 0; i < textures->count(); ++i) { m_textures->appendTexture(textures->source(i), textures->texture(i), textures->geometry(i), textures->clipRect(i), textures->flags(i)); } updateTexture(); m_textures->appendTexture(nullptr, m_bsTextureWrapper, window->geometry()); textures->lock(true); m_lockedWidgetTextures = textures; compositor->update(); return FlushSuccess; } void QOpenGLCompositorBackingStore::notifyComposited() { if (m_lockedWidgetTextures) { QPlatformTextureList *textureList = m_lockedWidgetTextures; m_lockedWidgetTextures = 0; // may reenter so null before unlocking textureList->lock(false); } } void QOpenGLCompositorBackingStore::beginPaint(const QRegion ®ion) { m_dirty |= region; if (m_image.hasAlphaChannel()) { QPainter p(&m_image); p.setCompositionMode(QPainter::CompositionMode_Source); for (const QRect &r : region) p.fillRect(r, Qt::transparent); } } void QOpenGLCompositorBackingStore::resize(const QSize &size, const QRegion &staticContents) { Q_UNUSED(staticContents); QOpenGLCompositor *compositor = QOpenGLCompositor::instance(); QOpenGLContext *dstCtx = compositor->context(); if (!dstCtx) return; QWindow *dstWin = compositor->targetWindow(); if (!dstWin) return; m_image = QImage(size, QImage::Format_RGBA8888); m_window->create(); dstCtx->makeCurrent(dstWin); if (m_bsTexture) { delete m_bsTextureWrapper; m_bsTextureWrapper = nullptr; glDeleteTextures(1, &m_bsTexture); m_bsTexture = 0; m_bsTextureContext = nullptr; } } QImage QOpenGLCompositorBackingStore::toImage() const { return m_image; } QT_END_NAMESPACE