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 *flushedWindow, const QRegion &region, const QPoint &offset)
138{
139 // Called for ordinary raster windows.
140
141 Q_UNUSED(region);
142 Q_UNUSED(offset);
143
144 QOpenGLCompositorWindow *handle = dynamic_cast<QOpenGLCompositorWindow *>(flushedWindow->handle());
145 if (handle && !handle->backingStore())
146 handle->setBackingStore(this);
147
148 if (!rhi(window: flushedWindow)) {
149 QPlatformBackingStoreRhiConfig rhiConfig;
150 rhiConfig.setApi(QPlatformBackingStoreRhiConfig::OpenGL);
151 rhiConfig.setEnabled(true);
152 createRhi(window: flushedWindow, config: rhiConfig);
153 }
154
155 static QPlatformTextureList emptyTextureList;
156 bool translucentBackground = m_image.hasAlphaChannel();
157 rhiFlush(window: flushedWindow, sourceDevicePixelRatio: flushedWindow->devicePixelRatio(),
158 region, offset, textures: &emptyTextureList, translucentBackground);
159}
160
161QPlatformBackingStore::FlushResult QOpenGLCompositorBackingStore::rhiFlush(QWindow *window,
162 qreal sourceDevicePixelRatio,
163 const QRegion &region,
164 const QPoint &offset,
165 QPlatformTextureList *textures,
166 bool translucentBackground)
167{
168 // QOpenGLWidget/QQuickWidget content provided as textures. The raster content goes on top.
169
170 Q_UNUSED(region);
171 Q_UNUSED(offset);
172 Q_UNUSED(translucentBackground);
173 Q_UNUSED(sourceDevicePixelRatio);
174
175 m_rhi = rhi(window);
176 Q_ASSERT(m_rhi);
177
178 QOpenGLCompositor *compositor = QOpenGLCompositor::instance();
179 QOpenGLContext *dstCtx = compositor->context();
180 if (!dstCtx)
181 return FlushFailed;
182
183 QWindow *dstWin = compositor->targetWindow();
184 if (!dstWin)
185 return FlushFailed;
186
187 if (!dstCtx->makeCurrent(surface: dstWin))
188 return FlushFailed;
189
190 QWindowPrivate::get(window)->lastComposeTime.start();
191
192 m_textures->clear();
193 for (int i = 0; i < textures->count(); ++i) {
194 m_textures->appendTexture(source: textures->source(index: i), texture: textures->texture(index: i), geometry: textures->geometry(index: i),
195 clipRect: textures->clipRect(index: i), flags: textures->flags(index: i));
196 }
197
198 updateTexture();
199 m_textures->appendTexture(source: nullptr, texture: m_bsTextureWrapper, geometry: window->geometry());
200
201 textures->lock(on: true);
202 m_lockedWidgetTextures = textures;
203
204 compositor->update();
205
206 return FlushSuccess;
207}
208
209void QOpenGLCompositorBackingStore::notifyComposited()
210{
211 if (m_lockedWidgetTextures) {
212 QPlatformTextureList *textureList = m_lockedWidgetTextures;
213 m_lockedWidgetTextures = 0; // may reenter so null before unlocking
214 textureList->lock(on: false);
215 }
216}
217
218void QOpenGLCompositorBackingStore::beginPaint(const QRegion &region)
219{
220 m_dirty |= region;
221
222 if (m_image.hasAlphaChannel()) {
223 QPainter p(&m_image);
224 p.setCompositionMode(QPainter::CompositionMode_Source);
225 for (const QRect &r : region)
226 p.fillRect(r, c: Qt::transparent);
227 }
228}
229
230void QOpenGLCompositorBackingStore::resize(const QSize &size, const QRegion &staticContents)
231{
232 Q_UNUSED(staticContents);
233
234 QOpenGLCompositor *compositor = QOpenGLCompositor::instance();
235 QOpenGLContext *dstCtx = compositor->context();
236 if (!dstCtx)
237 return;
238 QWindow *dstWin = compositor->targetWindow();
239 if (!dstWin)
240 return;
241
242 m_image = QImage(size, QImage::Format_RGBA8888);
243
244 m_window->create();
245
246 dstCtx->makeCurrent(surface: dstWin);
247 if (m_bsTexture) {
248 delete m_bsTextureWrapper;
249 m_bsTextureWrapper = nullptr;
250 glDeleteTextures(n: 1, textures: &m_bsTexture);
251 m_bsTexture = 0;
252 m_bsTextureContext = nullptr;
253 }
254}
255
256QImage QOpenGLCompositorBackingStore::toImage() const
257{
258 return m_image;
259}
260
261QT_END_NAMESPACE
262

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