1/****************************************************************************
2**
3** Copyright (C) 2020 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtGui module 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#ifndef QT_NO_OPENGL
41
42#include "qplatformbackingstoreopenglsupport.h"
43
44#include <QtGui/private/qwindow_p.h>
45
46#include <qpa/qplatformgraphicsbuffer.h>
47#include <qpa/qplatformgraphicsbufferhelper.h>
48
49#include <QtOpenGL/QOpenGLTextureBlitter>
50#include <QtGui/qopengl.h>
51#include <QtGui/QOpenGLFunctions>
52#include <QtGui/QOpenGLContext>
53#include <QtGui/QOffscreenSurface>
54
55#ifndef GL_TEXTURE_BASE_LEVEL
56#define GL_TEXTURE_BASE_LEVEL 0x813C
57#endif
58#ifndef GL_TEXTURE_MAX_LEVEL
59#define GL_TEXTURE_MAX_LEVEL 0x813D
60#endif
61#ifndef GL_UNPACK_ROW_LENGTH
62#define GL_UNPACK_ROW_LENGTH 0x0CF2
63#endif
64#ifndef GL_RGB10_A2
65#define GL_RGB10_A2 0x8059
66#endif
67#ifndef GL_UNSIGNED_INT_2_10_10_10_REV
68#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368
69#endif
70
71#ifndef GL_FRAMEBUFFER_SRGB
72#define GL_FRAMEBUFFER_SRGB 0x8DB9
73#endif
74#ifndef GL_FRAMEBUFFER_SRGB_CAPABLE
75#define GL_FRAMEBUFFER_SRGB_CAPABLE 0x8DBA
76#endif
77
78QT_BEGIN_NAMESPACE
79
80static inline QRect deviceRect(const QRect &rect, QWindow *window)
81{
82 QRect deviceRect(rect.topLeft() * window->devicePixelRatio(),
83 rect.size() * window->devicePixelRatio());
84 return deviceRect;
85}
86
87static inline QPoint deviceOffset(const QPoint &pt, QWindow *window)
88{
89 return pt * window->devicePixelRatio();
90}
91
92static QRegion deviceRegion(const QRegion &region, QWindow *window, const QPoint &offset)
93{
94 if (offset.isNull() && window->devicePixelRatio() <= 1)
95 return region;
96
97 QVarLengthArray<QRect, 4> rects;
98 rects.reserve(region.rectCount());
99 for (const QRect &rect : region)
100 rects.append(deviceRect(rect.translated(offset), window));
101
102 QRegion deviceRegion;
103 deviceRegion.setRects(rects.constData(), rects.count());
104 return deviceRegion;
105}
106
107static inline QRect toBottomLeftRect(const QRect &topLeftRect, int windowHeight)
108{
109 return QRect(topLeftRect.x(), windowHeight - topLeftRect.bottomRight().y() - 1,
110 topLeftRect.width(), topLeftRect.height());
111}
112
113static void blitTextureForWidget(const QPlatformTextureList *textures, int idx, QWindow *window, const QRect &deviceWindowRect,
114 QOpenGLTextureBlitter *blitter, const QPoint &offset, bool canUseSrgb)
115{
116 const QRect clipRect = textures->clipRect(idx);
117 if (clipRect.isEmpty())
118 return;
119
120 QRect rectInWindow = textures->geometry(idx);
121 // relative to the TLW, not necessarily our window (if the flush is for a native child widget), have to adjust
122 rectInWindow.translate(-offset);
123
124 const QRect clippedRectInWindow = rectInWindow & clipRect.translated(rectInWindow.topLeft());
125 const QRect srcRect = toBottomLeftRect(clipRect, rectInWindow.height());
126
127 const QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(deviceRect(clippedRectInWindow, window),
128 deviceWindowRect);
129
130 const QMatrix3x3 source = QOpenGLTextureBlitter::sourceTransform(deviceRect(srcRect, window),
131 deviceRect(rectInWindow, window).size(),
132 QOpenGLTextureBlitter::OriginBottomLeft);
133
134 QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
135 const bool srgb = textures->flags(idx).testFlag(QPlatformTextureList::TextureIsSrgb);
136 if (srgb && canUseSrgb)
137 funcs->glEnable(GL_FRAMEBUFFER_SRGB);
138
139 blitter->blit(textures->textureId(idx), target, source);
140
141 if (srgb && canUseSrgb)
142 funcs->glDisable(GL_FRAMEBUFFER_SRGB);
143}
144
145QPlatformBackingStoreOpenGLSupport::~QPlatformBackingStoreOpenGLSupport() {
146 if (context) {
147 QOffscreenSurface offscreenSurface;
148 offscreenSurface.setFormat(context->format());
149 offscreenSurface.create();
150 context->makeCurrent(&offscreenSurface);
151 if (textureId)
152 context->functions()->glDeleteTextures(1, &textureId);
153 if (blitter)
154 blitter->destroy();
155 }
156 delete blitter;
157}
158
159void QPlatformBackingStoreOpenGLSupport::composeAndFlush(QWindow *window, const QRegion &region, const QPoint &offset, QPlatformTextureList *textures, bool translucentBackground)
160{
161 if (!qt_window_private(window)->receivedExpose)
162 return;
163
164 if (!context) {
165 context.reset(new QOpenGLContext);
166 context->setFormat(window->requestedFormat());
167 context->setScreen(window->screen());
168 context->setShareContext(qt_window_private(window)->shareContext());
169 if (!context->create()) {
170 qCWarning(lcQpaBackingStore, "composeAndFlush: QOpenGLContext creation failed");
171 return;
172 }
173 }
174
175 bool current = context->makeCurrent(window);
176
177 if (!current && context->isValid()) {
178 delete blitter;
179 blitter = nullptr;
180 textureId = 0;
181 current = context->create() && context->makeCurrent(window);
182 }
183
184 if (!current) {
185 qCWarning(lcQpaBackingStore, "composeAndFlush: makeCurrent() failed");
186 return;
187 }
188
189 qCDebug(lcQpaBackingStore) << "Composing and flushing" << region << "of" << window
190 << "at offset" << offset << "with" << textures->count() << "texture(s) in" << textures;
191
192 QWindowPrivate::get(window)->lastComposeTime.start();
193
194 QOpenGLFunctions *funcs = context->functions();
195 funcs->glViewport(0, 0, qRound(window->width() * window->devicePixelRatio()), qRound(window->height() * window->devicePixelRatio()));
196 funcs->glClearColor(0, 0, 0, translucentBackground ? 0 : 1);
197 funcs->glClear(GL_COLOR_BUFFER_BIT);
198
199 if (!blitter) {
200 blitter = new QOpenGLTextureBlitter;
201 blitter->create();
202 }
203
204 blitter->bind();
205
206 const QRect deviceWindowRect = deviceRect(QRect(QPoint(), window->size()), window);
207 const QPoint deviceWindowOffset = deviceOffset(offset, window);
208
209 bool canUseSrgb = false;
210 // If there are any sRGB textures in the list, check if the destination
211 // framebuffer is sRGB capable.
212 for (int i = 0; i < textures->count(); ++i) {
213 if (textures->flags(i).testFlag(QPlatformTextureList::TextureIsSrgb)) {
214 GLint cap = 0;
215 funcs->glGetIntegerv(GL_FRAMEBUFFER_SRGB_CAPABLE, &cap);
216 if (cap)
217 canUseSrgb = true;
218 break;
219 }
220 }
221
222 // Textures for renderToTexture widgets.
223 for (int i = 0; i < textures->count(); ++i) {
224 if (!textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop))
225 blitTextureForWidget(textures, i, window, deviceWindowRect, blitter, offset, canUseSrgb);
226 }
227
228 // Backingstore texture with the normal widgets.
229 GLuint textureId = 0;
230 QOpenGLTextureBlitter::Origin origin = QOpenGLTextureBlitter::OriginTopLeft;
231 if (QPlatformGraphicsBuffer *graphicsBuffer = backingStore->graphicsBuffer()) {
232 if (graphicsBuffer->size() != textureSize) {
233 if (this->textureId)
234 funcs->glDeleteTextures(1, &this->textureId);
235 funcs->glGenTextures(1, &this->textureId);
236 funcs->glBindTexture(GL_TEXTURE_2D, this->textureId);
237 QOpenGLContext *ctx = QOpenGLContext::currentContext();
238 if (!ctx->isOpenGLES() || ctx->format().majorVersion() >= 3) {
239 funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
240 funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
241 }
242 funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
243 funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
244 funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
245 funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
246
247 if (QPlatformGraphicsBufferHelper::lockAndBindToTexture(graphicsBuffer, &needsSwizzle, &premultiplied)) {
248 textureSize = graphicsBuffer->size();
249 } else {
250 textureSize = QSize(0,0);
251 }
252
253 graphicsBuffer->unlock();
254 } else if (!region.isEmpty()){
255 funcs->glBindTexture(GL_TEXTURE_2D, this->textureId);
256 QPlatformGraphicsBufferHelper::lockAndBindToTexture(graphicsBuffer, &needsSwizzle, &premultiplied);
257 graphicsBuffer->unlock();
258 }
259
260 if (graphicsBuffer->origin() == QPlatformGraphicsBuffer::OriginBottomLeft)
261 origin = QOpenGLTextureBlitter::OriginBottomLeft;
262 textureId = this->textureId;
263 } else {
264 QPlatformBackingStore::TextureFlags flags;
265 textureId = backingStore->toTexture(deviceRegion(region, window, offset), &textureSize, &flags);
266 needsSwizzle = (flags & QPlatformBackingStore::TextureSwizzle) != 0;
267 premultiplied = (flags & QPlatformBackingStore::TexturePremultiplied) != 0;
268 if (flags & QPlatformBackingStore::TextureFlip)
269 origin = QOpenGLTextureBlitter::OriginBottomLeft;
270 }
271
272 funcs->glEnable(GL_BLEND);
273 if (premultiplied)
274 funcs->glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
275 else
276 funcs->glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
277
278 if (textureId) {
279 if (needsSwizzle)
280 blitter->setRedBlueSwizzle(true);
281 // The backingstore is for the entire tlw.
282 // In case of native children offset tells the position relative to the tlw.
283 const QRect srcRect = toBottomLeftRect(deviceWindowRect.translated(deviceWindowOffset), textureSize.height());
284 const QMatrix3x3 source = QOpenGLTextureBlitter::sourceTransform(srcRect,
285 textureSize,
286 origin);
287 blitter->blit(textureId, QMatrix4x4(), source);
288 if (needsSwizzle)
289 blitter->setRedBlueSwizzle(false);
290 }
291
292 // Textures for renderToTexture widgets that have WA_AlwaysStackOnTop set.
293 bool blendIsPremultiplied = premultiplied;
294 for (int i = 0; i < textures->count(); ++i) {
295 const QPlatformTextureList::Flags flags = textures->flags(i);
296 if (flags.testFlag(QPlatformTextureList::NeedsPremultipliedAlphaBlending)) {
297 if (!blendIsPremultiplied) {
298 funcs->glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
299 blendIsPremultiplied = true;
300 }
301 } else {
302 if (blendIsPremultiplied) {
303 funcs->glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
304 blendIsPremultiplied = false;
305 }
306 }
307 if (flags.testFlag(QPlatformTextureList::StacksOnTop))
308 blitTextureForWidget(textures, i, window, deviceWindowRect, blitter, offset, canUseSrgb);
309 }
310
311 funcs->glDisable(GL_BLEND);
312 blitter->release();
313
314 context->swapBuffers(window);
315}
316
317GLuint QPlatformBackingStoreOpenGLSupport::toTexture(const QRegion &dirtyRegion, QSize *textureSize, QPlatformBackingStore::TextureFlags *flags) const
318{
319 Q_ASSERT(textureSize);
320 Q_ASSERT(flags);
321
322 QImage image = backingStore->toImage();
323 QSize imageSize = image.size();
324
325 QOpenGLContext *ctx = QOpenGLContext::currentContext();
326 GLenum internalFormat = GL_RGBA;
327 GLuint pixelType = GL_UNSIGNED_BYTE;
328
329 bool needsConversion = false;
330 *flags = { };
331 switch (image.format()) {
332 case QImage::Format_ARGB32_Premultiplied:
333 *flags |= QPlatformBackingStore::TexturePremultiplied;
334 Q_FALLTHROUGH();
335 case QImage::Format_RGB32:
336 case QImage::Format_ARGB32:
337 *flags |= QPlatformBackingStore::TextureSwizzle;
338 break;
339 case QImage::Format_RGBA8888_Premultiplied:
340 *flags |= QPlatformBackingStore::TexturePremultiplied;
341 Q_FALLTHROUGH();
342 case QImage::Format_RGBX8888:
343 case QImage::Format_RGBA8888:
344 break;
345 case QImage::Format_BGR30:
346 case QImage::Format_A2BGR30_Premultiplied:
347 if (!ctx->isOpenGLES() || ctx->format().majorVersion() >= 3) {
348 pixelType = GL_UNSIGNED_INT_2_10_10_10_REV;
349 internalFormat = GL_RGB10_A2;
350 *flags |= QPlatformBackingStore::TexturePremultiplied;
351 } else {
352 needsConversion = true;
353 }
354 break;
355 case QImage::Format_RGB30:
356 case QImage::Format_A2RGB30_Premultiplied:
357 if (!ctx->isOpenGLES() || ctx->format().majorVersion() >= 3) {
358 pixelType = GL_UNSIGNED_INT_2_10_10_10_REV;
359 internalFormat = GL_RGB10_A2;
360 *flags |= QPlatformBackingStore::TextureSwizzle | QPlatformBackingStore::TexturePremultiplied;
361 } else {
362 needsConversion = true;
363 }
364 break;
365 default:
366 needsConversion = true;
367 break;
368 }
369 if (imageSize.isEmpty()) {
370 *textureSize = imageSize;
371 return 0;
372 }
373
374 // Must rely on the input only, not d_ptr.
375 // With the default composeAndFlush() textureSize is &d_ptr->textureSize.
376 bool resized = *textureSize != imageSize;
377 if (dirtyRegion.isEmpty() && !resized)
378 return textureId;
379
380 *textureSize = imageSize;
381
382 if (needsConversion)
383 image = image.convertToFormat(QImage::Format_RGBA8888);
384
385 // The image provided by the backingstore may have a stride larger than width * 4, for
386 // instance on platforms that manually implement client-side decorations.
387 static const int bytesPerPixel = 4;
388 const qsizetype strideInPixels = image.bytesPerLine() / bytesPerPixel;
389 const bool hasUnpackRowLength = !ctx->isOpenGLES() || ctx->format().majorVersion() >= 3;
390
391 QOpenGLFunctions *funcs = ctx->functions();
392
393 if (hasUnpackRowLength) {
394 funcs->glPixelStorei(GL_UNPACK_ROW_LENGTH, strideInPixels);
395 } else if (strideInPixels != image.width()) {
396 // No UNPACK_ROW_LENGTH on ES 2.0 and yet we would need it. This case is typically
397 // hit with QtWayland which is rarely used in combination with a ES2.0-only GL
398 // implementation. Therefore, accept the performance hit and do a copy.
399 image = image.copy();
400 }
401
402 if (resized) {
403 if (textureId)
404 funcs->glDeleteTextures(1, &textureId);
405 funcs->glGenTextures(1, &textureId);
406 funcs->glBindTexture(GL_TEXTURE_2D, textureId);
407 if (!ctx->isOpenGLES() || ctx->format().majorVersion() >= 3) {
408 funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
409 funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
410 }
411 funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
412 funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
413 funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
414 funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
415
416 funcs->glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, imageSize.width(), imageSize.height(), 0, GL_RGBA, pixelType,
417 const_cast<uchar*>(image.constBits()));
418 } else {
419 funcs->glBindTexture(GL_TEXTURE_2D, textureId);
420 QRect imageRect = image.rect();
421 QRect rect = dirtyRegion.boundingRect() & imageRect;
422
423 if (hasUnpackRowLength) {
424 funcs->glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x(), rect.y(), rect.width(), rect.height(), GL_RGBA, pixelType,
425 image.constScanLine(rect.y()) + rect.x() * bytesPerPixel);
426 } else {
427 // if the rect is wide enough it's cheaper to just
428 // extend it instead of doing an image copy
429 if (rect.width() >= imageRect.width() / 2) {
430 rect.setX(0);
431 rect.setWidth(imageRect.width());
432 }
433
434 // if the sub-rect is full-width we can pass the image data directly to
435 // OpenGL instead of copying, since there's no gap between scanlines
436
437 if (rect.width() == imageRect.width()) {
438 funcs->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, rect.y(), rect.width(), rect.height(), GL_RGBA, pixelType,
439 image.constScanLine(rect.y()));
440 } else {
441 funcs->glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x(), rect.y(), rect.width(), rect.height(), GL_RGBA, pixelType,
442 image.copy(rect).constBits());
443 }
444 }
445 }
446
447 if (hasUnpackRowLength)
448 funcs->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
449
450 return textureId;
451}
452
453void qt_registerDefaultPlatformBackingStoreOpenGLSupport()
454{
455 if (!QPlatformBackingStoreOpenGLSupportBase::factoryFunction()) {
456 QPlatformBackingStoreOpenGLSupportBase::setFactoryFunction([]() -> QPlatformBackingStoreOpenGLSupportBase* {
457 return new QPlatformBackingStoreOpenGLSupport;
458 });
459 }
460}
461
462#endif // QT_NO_OPENGL
463
464QT_END_NAMESPACE
465