1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the plugins of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include <QtGui/QOpenGLContext>
41#include <QtGui/QWindow>
42#include <QtGui/QPainter>
43#include <QtGui/QOffscreenSurface>
44#include <qpa/qplatformbackingstore.h>
45#include <private/qwindow_p.h>
46
47#include "qopenglcompositorbackingstore_p.h"
48#include "qopenglcompositor_p.h"
49
50#ifndef GL_UNPACK_ROW_LENGTH
51#define GL_UNPACK_ROW_LENGTH 0x0CF2
52#endif
53
54QT_BEGIN_NAMESPACE
55
56/*!
57 \class QOpenGLCompositorBackingStore
58 \brief A backing store implementation for OpenGL
59 \since 5.4
60 \internal
61 \ingroup qpa
62
63 This implementation uploads raster-rendered widget windows into
64 textures. It is meant to be used with QOpenGLCompositor that
65 composites the textures onto a single native window using OpenGL.
66 This means that multiple top-level widgets are supported without
67 creating actual native windows for each of them.
68
69 \note It is important to call notifyComposited() from the
70 corresponding platform window's endCompositing() callback
71 (inherited from QOpenGLCompositorWindow).
72
73 \note When implementing QOpenGLCompositorWindow::textures() for
74 windows of type RasterSurface or RasterGLSurface, simply return
75 the list provided by this class' textures().
76*/
77
78QOpenGLCompositorBackingStore::QOpenGLCompositorBackingStore(QWindow *window)
79 : QPlatformBackingStore(window),
80 m_window(window),
81 m_bsTexture(0),
82 m_bsTextureContext(0),
83 m_textures(new QPlatformTextureList),
84 m_lockedWidgetTextures(0)
85{
86}
87
88QOpenGLCompositorBackingStore::~QOpenGLCompositorBackingStore()
89{
90 if (m_bsTexture) {
91 QOpenGLContext *ctx = QOpenGLContext::currentContext();
92 // With render-to-texture-widgets QWidget makes sure the TLW's shareContext() is
93 // made current before destroying backingstores. That is however not the case for
94 // windows with regular widgets only.
95 QScopedPointer<QOffscreenSurface> tempSurface;
96 if (!ctx) {
97 ctx = QOpenGLCompositor::instance()->context();
98 tempSurface.reset(new QOffscreenSurface);
99 tempSurface->setFormat(ctx->format());
100 tempSurface->create();
101 ctx->makeCurrent(tempSurface.data());
102 }
103
104 if (m_bsTextureContext && ctx->shareGroup() == m_bsTextureContext->shareGroup())
105 glDeleteTextures(1, &m_bsTexture);
106 else
107 qWarning("QOpenGLCompositorBackingStore: Texture is not valid in the current context");
108
109 if (tempSurface)
110 ctx->doneCurrent();
111 }
112
113 delete m_textures; // this does not actually own any GL resources
114}
115
116QPaintDevice *QOpenGLCompositorBackingStore::paintDevice()
117{
118 return &m_image;
119}
120
121void QOpenGLCompositorBackingStore::updateTexture()
122{
123 if (!m_bsTexture) {
124 m_bsTextureContext = QOpenGLContext::currentContext();
125 Q_ASSERT(m_bsTextureContext);
126 glGenTextures(1, &m_bsTexture);
127 glBindTexture(GL_TEXTURE_2D, m_bsTexture);
128 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
129 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
130 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
131 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
132 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_image.width(), m_image.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
133 } else {
134 glBindTexture(GL_TEXTURE_2D, m_bsTexture);
135 }
136
137 if (!m_dirty.isNull()) {
138 QRegion fixed;
139 QRect imageRect = m_image.rect();
140
141 QOpenGLContext *ctx = QOpenGLContext::currentContext();
142 if (!ctx->isOpenGLES() || ctx->format().majorVersion() >= 3) {
143 for (const QRect &rect : m_dirty) {
144 QRect r = imageRect & rect;
145 glPixelStorei(GL_UNPACK_ROW_LENGTH, m_image.width());
146 glTexSubImage2D(GL_TEXTURE_2D, 0, r.x(), r.y(), r.width(), r.height(), GL_RGBA, GL_UNSIGNED_BYTE,
147 m_image.constScanLine(r.y()) + r.x() * 4);
148 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
149 }
150 } else {
151 for (const QRect &rect : m_dirty) {
152 // intersect with image rect to be sure
153 QRect r = imageRect & rect;
154
155 // if the rect is wide enough it's cheaper to just
156 // extend it instead of doing an image copy
157 if (r.width() >= imageRect.width() / 2) {
158 r.setX(0);
159 r.setWidth(imageRect.width());
160 }
161
162 fixed |= r;
163 }
164 for (const QRect &rect : fixed) {
165 // if the sub-rect is full-width we can pass the image data directly to
166 // OpenGL instead of copying, since there's no gap between scanlines
167 if (rect.width() == imageRect.width()) {
168 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, rect.y(), rect.width(), rect.height(), GL_RGBA, GL_UNSIGNED_BYTE,
169 m_image.constScanLine(rect.y()));
170 } else {
171 glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x(), rect.y(), rect.width(), rect.height(), GL_RGBA, GL_UNSIGNED_BYTE,
172 m_image.copy(rect).constBits());
173 }
174 }
175 }
176
177 m_dirty = QRegion();
178 }
179}
180
181void QOpenGLCompositorBackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
182{
183 // Called for ordinary raster windows.
184
185 Q_UNUSED(region);
186 Q_UNUSED(offset);
187
188 QOpenGLCompositor *compositor = QOpenGLCompositor::instance();
189 QOpenGLContext *dstCtx = compositor->context();
190 Q_ASSERT(dstCtx);
191
192 QWindow *dstWin = compositor->targetWindow();
193 if (!dstWin)
194 return;
195
196 dstCtx->makeCurrent(dstWin);
197 updateTexture();
198 m_textures->clear();
199 m_textures->appendTexture(nullptr, m_bsTexture, window->geometry());
200
201 compositor->update();
202}
203
204void QOpenGLCompositorBackingStore::composeAndFlush(QWindow *window, const QRegion &region, const QPoint &offset,
205 QPlatformTextureList *textures,
206 bool translucentBackground)
207{
208 // QOpenGLWidget/QQuickWidget content provided as textures. The raster content goes on top.
209
210 Q_UNUSED(region);
211 Q_UNUSED(offset);
212 Q_UNUSED(translucentBackground);
213
214 QOpenGLCompositor *compositor = QOpenGLCompositor::instance();
215 QOpenGLContext *dstCtx = compositor->context();
216 Q_ASSERT(dstCtx); // setTarget() must have been called before, e.g. from QEGLFSWindow
217
218 // The compositor's context and the context to which QOpenGLWidget/QQuickWidget
219 // textures belong are not the same. They share resources, though.
220 Q_ASSERT(qt_window_private(window)->shareContext()->shareGroup() == dstCtx->shareGroup());
221
222 QWindow *dstWin = compositor->targetWindow();
223 if (!dstWin)
224 return;
225
226 dstCtx->makeCurrent(dstWin);
227
228 QWindowPrivate::get(window)->lastComposeTime.start();
229
230 m_textures->clear();
231 for (int i = 0; i < textures->count(); ++i)
232 m_textures->appendTexture(textures->source(i), textures->textureId(i), textures->geometry(i),
233 textures->clipRect(i), textures->flags(i));
234
235 updateTexture();
236 m_textures->appendTexture(nullptr, m_bsTexture, window->geometry());
237
238 textures->lock(true);
239 m_lockedWidgetTextures = textures;
240
241 compositor->update();
242}
243
244void QOpenGLCompositorBackingStore::notifyComposited()
245{
246 if (m_lockedWidgetTextures) {
247 QPlatformTextureList *textureList = m_lockedWidgetTextures;
248 m_lockedWidgetTextures = 0; // may reenter so null before unlocking
249 textureList->lock(false);
250 }
251}
252
253void QOpenGLCompositorBackingStore::beginPaint(const QRegion &region)
254{
255 m_dirty |= region;
256
257 if (m_image.hasAlphaChannel()) {
258 QPainter p(&m_image);
259 p.setCompositionMode(QPainter::CompositionMode_Source);
260 for (const QRect &r : region)
261 p.fillRect(r, Qt::transparent);
262 }
263}
264
265void QOpenGLCompositorBackingStore::resize(const QSize &size, const QRegion &staticContents)
266{
267 Q_UNUSED(staticContents);
268
269 QOpenGLCompositor *compositor = QOpenGLCompositor::instance();
270 QOpenGLContext *dstCtx = compositor->context();
271 QWindow *dstWin = compositor->targetWindow();
272 if (!dstWin)
273 return;
274
275 m_image = QImage(size, QImage::Format_RGBA8888);
276
277 m_window->create();
278
279 dstCtx->makeCurrent(dstWin);
280 if (m_bsTexture) {
281 glDeleteTextures(1, &m_bsTexture);
282 m_bsTexture = 0;
283 m_bsTextureContext = nullptr;
284 }
285}
286
287QImage QOpenGLCompositorBackingStore::toImage() const
288{
289 return m_image;
290}
291
292QT_END_NAMESPACE
293