1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <QtGui/QOpenGLContext>
5#include <QtGui/QWindow>
6#include <QtGui/QPainter>
7#include <qpa/qplatformbackingstore.h>
8#include <private/qwindow_p.h>
9#include <rhi/qrhi.h>
10
11#include "qopenglcompositorbackingstore_p.h"
12#include "qopenglcompositor_p.h"
13
14#ifndef GL_UNPACK_ROW_LENGTH
15#define GL_UNPACK_ROW_LENGTH 0x0CF2
16#endif
17
18QT_BEGIN_NAMESPACE
19
20/*!
21 \class QOpenGLCompositorBackingStore
22 \brief A backing store implementation for OpenGL
23 \since 5.4
24 \internal
25 \ingroup qpa
26
27 This implementation uploads raster-rendered widget windows into
28 textures. It is meant to be used with QOpenGLCompositor that
29 composites the textures onto a single native window using OpenGL.
30 This means that multiple top-level widgets are supported without
31 creating actual native windows for each of them.
32
33 \note It is important to call notifyComposited() from the
34 corresponding platform window's endCompositing() callback
35 (inherited from QOpenGLCompositorWindow).
36
37 \note When implementing QOpenGLCompositorWindow::textures() for
38 windows of type RasterSurface or RasterGLSurface, simply return
39 the list provided by this class' textures().
40*/
41
42QOpenGLCompositorBackingStore::QOpenGLCompositorBackingStore(QWindow *window)
43 : QPlatformBackingStore(window),
44 m_window(window),
45 m_bsTexture(0),
46 m_bsTextureWrapper(nullptr),
47 m_bsTextureContext(0),
48 m_textures(new QPlatformTextureList),
49 m_lockedWidgetTextures(0),
50 m_rhi(nullptr)
51{
52}
53
54QOpenGLCompositorBackingStore::~QOpenGLCompositorBackingStore()
55{
56 if (m_bsTexture && m_rhi) {
57 delete m_bsTextureWrapper;
58 // Contexts are sharing resources, won't matter which one is
59 // current here, use the rhi's shortcut.
60 m_rhi->makeThreadLocalNativeContextCurrent();
61 glDeleteTextures(n: 1, textures: &m_bsTexture);
62 }
63
64 delete m_textures; // this does not actually own any GL resources
65}
66
67QPaintDevice *QOpenGLCompositorBackingStore::paintDevice()
68{
69 return &m_image;
70}
71
72void QOpenGLCompositorBackingStore::updateTexture()
73{
74 if (!m_bsTexture) {
75 m_bsTextureContext = QOpenGLContext::currentContext();
76 Q_ASSERT(m_bsTextureContext);
77 glGenTextures(n: 1, textures: &m_bsTexture);
78 glBindTexture(GL_TEXTURE_2D, texture: m_bsTexture);
79 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
80 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
81 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
82 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
83 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);
84 } else {
85 glBindTexture(GL_TEXTURE_2D, texture: m_bsTexture);
86 }
87
88 if (!m_dirty.isNull()) {
89 QRegion fixed;
90 QRect imageRect = m_image.rect();
91
92 QOpenGLContext *ctx = QOpenGLContext::currentContext();
93 if (!ctx->isOpenGLES() || ctx->format().majorVersion() >= 3) {
94 for (const QRect &rect : m_dirty) {
95 QRect r = imageRect & rect;
96 glPixelStorei(GL_UNPACK_ROW_LENGTH, param: m_image.width());
97 glTexSubImage2D(GL_TEXTURE_2D, level: 0, xoffset: r.x(), yoffset: r.y(), width: r.width(), height: r.height(), GL_RGBA, GL_UNSIGNED_BYTE,
98 pixels: m_image.constScanLine(r.y()) + r.x() * 4);
99 glPixelStorei(GL_UNPACK_ROW_LENGTH, param: 0);
100 }
101 } else {
102 for (const QRect &rect : m_dirty) {
103 // intersect with image rect to be sure
104 QRect r = imageRect & rect;
105
106 // if the rect is wide enough it's cheaper to just
107 // extend it instead of doing an image copy
108 if (r.width() >= imageRect.width() / 2) {
109 r.setX(0);
110 r.setWidth(imageRect.width());
111 }
112
113 fixed |= r;
114 }
115 for (const QRect &rect : fixed) {
116 // if the sub-rect is full-width we can pass the image data directly to
117 // OpenGL instead of copying, since there's no gap between scanlines
118 if (rect.width() == imageRect.width()) {
119 glTexSubImage2D(GL_TEXTURE_2D, level: 0, xoffset: 0, yoffset: rect.y(), width: rect.width(), height: rect.height(), GL_RGBA, GL_UNSIGNED_BYTE,
120 pixels: m_image.constScanLine(rect.y()));
121 } else {
122 glTexSubImage2D(GL_TEXTURE_2D, level: 0, xoffset: rect.x(), yoffset: rect.y(), width: rect.width(), height: rect.height(), GL_RGBA, GL_UNSIGNED_BYTE,
123 pixels: m_image.copy(rect).constBits());
124 }
125 }
126 }
127
128 m_dirty = QRegion();
129 }
130
131 if (!m_bsTextureWrapper) {
132 m_bsTextureWrapper = m_rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: m_image.size());
133 m_bsTextureWrapper->createFrom(src: {.object: m_bsTexture, .layout: 0});
134 }
135}
136
137void QOpenGLCompositorBackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
138{
139 // Called for ordinary raster windows.
140
141 Q_UNUSED(region);
142 Q_UNUSED(offset);
143
144 m_rhi = rhi();
145 if (!m_rhi) {
146 setRhiConfig(QPlatformBackingStoreRhiConfig(QPlatformBackingStoreRhiConfig::OpenGL));
147 m_rhi = rhi();
148 }
149 Q_ASSERT(m_rhi);
150
151 QOpenGLCompositor *compositor = QOpenGLCompositor::instance();
152 QOpenGLContext *dstCtx = compositor->context();
153 if (!dstCtx)
154 return;
155
156 QWindow *dstWin = compositor->targetWindow();
157 if (!dstWin)
158 return;
159
160 if (!dstCtx->makeCurrent(surface: dstWin))
161 return;
162
163 updateTexture();
164 m_textures->clear();
165 m_textures->appendTexture(source: nullptr, texture: m_bsTextureWrapper, geometry: window->geometry());
166
167 compositor->update();
168}
169
170QPlatformBackingStore::FlushResult QOpenGLCompositorBackingStore::rhiFlush(QWindow *window,
171 qreal sourceDevicePixelRatio,
172 const QRegion &region,
173 const QPoint &offset,
174 QPlatformTextureList *textures,
175 bool translucentBackground)
176{
177 // QOpenGLWidget/QQuickWidget content provided as textures. The raster content goes on top.
178
179 Q_UNUSED(region);
180 Q_UNUSED(offset);
181 Q_UNUSED(translucentBackground);
182 Q_UNUSED(sourceDevicePixelRatio);
183
184 m_rhi = rhi();
185 if (!m_rhi) {
186 setRhiConfig(QPlatformBackingStoreRhiConfig(QPlatformBackingStoreRhiConfig::OpenGL));
187 m_rhi = rhi();
188 }
189 Q_ASSERT(m_rhi);
190
191 QOpenGLCompositor *compositor = QOpenGLCompositor::instance();
192 QOpenGLContext *dstCtx = compositor->context();
193 if (!dstCtx)
194 return FlushFailed;
195
196 QWindow *dstWin = compositor->targetWindow();
197 if (!dstWin)
198 return FlushFailed;
199
200 if (!dstCtx->makeCurrent(surface: dstWin))
201 return FlushFailed;
202
203 QWindowPrivate::get(window)->lastComposeTime.start();
204
205 m_textures->clear();
206 for (int i = 0; i < textures->count(); ++i) {
207 m_textures->appendTexture(source: textures->source(index: i), texture: textures->texture(index: i), geometry: textures->geometry(index: i),
208 clipRect: textures->clipRect(index: i), flags: textures->flags(index: i));
209 }
210
211 updateTexture();
212 m_textures->appendTexture(source: nullptr, texture: m_bsTextureWrapper, geometry: window->geometry());
213
214 textures->lock(on: true);
215 m_lockedWidgetTextures = textures;
216
217 compositor->update();
218
219 return FlushSuccess;
220}
221
222void QOpenGLCompositorBackingStore::notifyComposited()
223{
224 if (m_lockedWidgetTextures) {
225 QPlatformTextureList *textureList = m_lockedWidgetTextures;
226 m_lockedWidgetTextures = 0; // may reenter so null before unlocking
227 textureList->lock(on: false);
228 }
229}
230
231void QOpenGLCompositorBackingStore::beginPaint(const QRegion &region)
232{
233 m_dirty |= region;
234
235 if (m_image.hasAlphaChannel()) {
236 QPainter p(&m_image);
237 p.setCompositionMode(QPainter::CompositionMode_Source);
238 for (const QRect &r : region)
239 p.fillRect(r, c: Qt::transparent);
240 }
241}
242
243void QOpenGLCompositorBackingStore::resize(const QSize &size, const QRegion &staticContents)
244{
245 Q_UNUSED(staticContents);
246
247 QOpenGLCompositor *compositor = QOpenGLCompositor::instance();
248 QOpenGLContext *dstCtx = compositor->context();
249 QWindow *dstWin = compositor->targetWindow();
250 if (!dstWin)
251 return;
252
253 m_image = QImage(size, QImage::Format_RGBA8888);
254
255 m_window->create();
256
257 dstCtx->makeCurrent(surface: dstWin);
258 if (m_bsTexture) {
259 delete m_bsTextureWrapper;
260 m_bsTextureWrapper = nullptr;
261 glDeleteTextures(n: 1, textures: &m_bsTexture);
262 m_bsTexture = 0;
263 m_bsTextureContext = nullptr;
264 }
265}
266
267QImage QOpenGLCompositorBackingStore::toImage() const
268{
269 return m_image;
270}
271
272QT_END_NAMESPACE
273

source code of qtbase/src/opengl/qopenglcompositorbackingstore.cpp