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 | |
54 | QT_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 | |
78 | QOpenGLCompositorBackingStore::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 | |
88 | QOpenGLCompositorBackingStore::~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(other: new QOffscreenSurface); |
99 | tempSurface->setFormat(ctx->format()); |
100 | tempSurface->create(); |
101 | ctx->makeCurrent(surface: tempSurface.data()); |
102 | } |
103 | |
104 | if (m_bsTextureContext && ctx->shareGroup() == m_bsTextureContext->shareGroup()) |
105 | glDeleteTextures(n: 1, textures: &m_bsTexture); |
106 | else |
107 | qWarning(msg: "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 | |
116 | QPaintDevice *QOpenGLCompositorBackingStore::paintDevice() |
117 | { |
118 | return &m_image; |
119 | } |
120 | |
121 | void QOpenGLCompositorBackingStore::updateTexture() |
122 | { |
123 | if (!m_bsTexture) { |
124 | m_bsTextureContext = QOpenGLContext::currentContext(); |
125 | Q_ASSERT(m_bsTextureContext); |
126 | glGenTextures(n: 1, textures: &m_bsTexture); |
127 | glBindTexture(GL_TEXTURE_2D, texture: 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, level: 0, GL_RGBA, width: m_image.width(), height: m_image.height(), border: 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels: 0); |
133 | } else { |
134 | glBindTexture(GL_TEXTURE_2D, texture: 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, param: m_image.width()); |
146 | glTexSubImage2D(GL_TEXTURE_2D, level: 0, xoffset: r.x(), yoffset: r.y(), width: r.width(), height: r.height(), GL_RGBA, GL_UNSIGNED_BYTE, |
147 | pixels: m_image.constScanLine(r.y()) + r.x() * 4); |
148 | glPixelStorei(GL_UNPACK_ROW_LENGTH, param: 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, level: 0, xoffset: 0, yoffset: rect.y(), width: rect.width(), height: rect.height(), GL_RGBA, GL_UNSIGNED_BYTE, |
169 | pixels: m_image.constScanLine(rect.y())); |
170 | } else { |
171 | glTexSubImage2D(GL_TEXTURE_2D, level: 0, xoffset: rect.x(), yoffset: rect.y(), width: rect.width(), height: rect.height(), GL_RGBA, GL_UNSIGNED_BYTE, |
172 | pixels: m_image.copy(rect).constBits()); |
173 | } |
174 | } |
175 | } |
176 | |
177 | m_dirty = QRegion(); |
178 | } |
179 | } |
180 | |
181 | void QOpenGLCompositorBackingStore::flush(QWindow *window, const QRegion ®ion, 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(surface: dstWin); |
197 | updateTexture(); |
198 | m_textures->clear(); |
199 | m_textures->appendTexture(source: nullptr, textureId: m_bsTexture, geometry: window->geometry()); |
200 | |
201 | compositor->update(); |
202 | } |
203 | |
204 | void QOpenGLCompositorBackingStore::composeAndFlush(QWindow *window, const QRegion ®ion, 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(surface: 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(source: textures->source(index: i), textureId: textures->textureId(index: i), geometry: textures->geometry(index: i), |
233 | clipRect: textures->clipRect(index: i), flags: textures->flags(index: i)); |
234 | |
235 | updateTexture(); |
236 | m_textures->appendTexture(source: nullptr, textureId: m_bsTexture, geometry: window->geometry()); |
237 | |
238 | textures->lock(on: true); |
239 | m_lockedWidgetTextures = textures; |
240 | |
241 | compositor->update(); |
242 | } |
243 | |
244 | void 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(on: false); |
250 | } |
251 | } |
252 | |
253 | void QOpenGLCompositorBackingStore::beginPaint(const QRegion ®ion) |
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, c: Qt::transparent); |
262 | } |
263 | } |
264 | |
265 | void 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(surface: dstWin); |
280 | if (m_bsTexture) { |
281 | glDeleteTextures(n: 1, textures: &m_bsTexture); |
282 | m_bsTexture = 0; |
283 | m_bsTextureContext = nullptr; |
284 | } |
285 | } |
286 | |
287 | QImage QOpenGLCompositorBackingStore::toImage() const |
288 | { |
289 | return m_image; |
290 | } |
291 | |
292 | QT_END_NAMESPACE |
293 | |