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 examples of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:BSD$ |
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 | ** BSD License Usage |
18 | ** Alternatively, you may use this file under the terms of the BSD license |
19 | ** as follows: |
20 | ** |
21 | ** "Redistribution and use in source and binary forms, with or without |
22 | ** modification, are permitted provided that the following conditions are |
23 | ** met: |
24 | ** * Redistributions of source code must retain the above copyright |
25 | ** notice, this list of conditions and the following disclaimer. |
26 | ** * Redistributions in binary form must reproduce the above copyright |
27 | ** notice, this list of conditions and the following disclaimer in |
28 | ** the documentation and/or other materials provided with the |
29 | ** distribution. |
30 | ** * Neither the name of The Qt Company Ltd nor the names of its |
31 | ** contributors may be used to endorse or promote products derived |
32 | ** from this software without specific prior written permission. |
33 | ** |
34 | ** |
35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." |
46 | ** |
47 | ** $QT_END_LICENSE$ |
48 | ** |
49 | ****************************************************************************/ |
50 | |
51 | #include "threadrenderer.h" |
52 | #include "logorenderer.h" |
53 | |
54 | #include <QtCore/QMutex> |
55 | #include <QtCore/QThread> |
56 | |
57 | #include <QtGui/QOpenGLContext> |
58 | #include <QtGui/QOpenGLFramebufferObject> |
59 | #include <QtGui/QGuiApplication> |
60 | #include <QtGui/QOffscreenSurface> |
61 | |
62 | #include <QtQuick/QQuickWindow> |
63 | #include <qsgsimpletexturenode.h> |
64 | |
65 | QList<QThread *> ThreadRenderer::threads; |
66 | |
67 | /* |
68 | * The render thread shares a context with the scene graph and will |
69 | * render into two separate FBOs, one to use for display and one |
70 | * to use for rendering |
71 | */ |
72 | class RenderThread : public QThread |
73 | { |
74 | Q_OBJECT |
75 | public: |
76 | RenderThread(const QSize &size) |
77 | : surface(nullptr) |
78 | , context(nullptr) |
79 | , m_renderFbo(nullptr) |
80 | , m_displayFbo(nullptr) |
81 | , m_logoRenderer(nullptr) |
82 | , m_size(size) |
83 | { |
84 | ThreadRenderer::threads << this; |
85 | } |
86 | |
87 | QOffscreenSurface *surface; |
88 | QOpenGLContext *context; |
89 | |
90 | public slots: |
91 | void renderNext() |
92 | { |
93 | context->makeCurrent(surface); |
94 | |
95 | if (!m_renderFbo) { |
96 | // Initialize the buffers and renderer |
97 | QOpenGLFramebufferObjectFormat format; |
98 | format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); |
99 | m_renderFbo = new QOpenGLFramebufferObject(m_size, format); |
100 | m_displayFbo = new QOpenGLFramebufferObject(m_size, format); |
101 | m_logoRenderer = new LogoRenderer(); |
102 | m_logoRenderer->initialize(); |
103 | } |
104 | |
105 | m_renderFbo->bind(); |
106 | context->functions()->glViewport(x: 0, y: 0, width: m_size.width(), height: m_size.height()); |
107 | |
108 | m_logoRenderer->render(); |
109 | |
110 | // We need to flush the contents to the FBO before posting |
111 | // the texture to the other thread, otherwise, we might |
112 | // get unexpected results. |
113 | context->functions()->glFlush(); |
114 | |
115 | m_renderFbo->bindDefault(); |
116 | qSwap(value1&: m_renderFbo, value2&: m_displayFbo); |
117 | |
118 | emit textureReady(id: m_displayFbo->texture(), size: m_size); |
119 | } |
120 | |
121 | void shutDown() |
122 | { |
123 | context->makeCurrent(surface); |
124 | delete m_renderFbo; |
125 | delete m_displayFbo; |
126 | delete m_logoRenderer; |
127 | context->doneCurrent(); |
128 | delete context; |
129 | |
130 | // schedule this to be deleted only after we're done cleaning up |
131 | surface->deleteLater(); |
132 | |
133 | // Stop event processing, move the thread to GUI and make sure it is deleted. |
134 | exit(); |
135 | moveToThread(thread: QGuiApplication::instance()->thread()); |
136 | } |
137 | |
138 | signals: |
139 | void textureReady(int id, const QSize &size); |
140 | |
141 | private: |
142 | QOpenGLFramebufferObject *m_renderFbo; |
143 | QOpenGLFramebufferObject *m_displayFbo; |
144 | |
145 | LogoRenderer *m_logoRenderer; |
146 | QSize m_size; |
147 | }; |
148 | |
149 | |
150 | |
151 | class TextureNode : public QObject, public QSGSimpleTextureNode |
152 | { |
153 | Q_OBJECT |
154 | |
155 | public: |
156 | TextureNode(QQuickWindow *window) |
157 | : m_id(0) |
158 | , m_size(0, 0) |
159 | , m_texture(nullptr) |
160 | , m_window(window) |
161 | { |
162 | // Our texture node must have a texture, so use the default 0 texture. |
163 | m_texture = m_window->createTextureFromId(id: 0, size: QSize(1, 1)); |
164 | setTexture(m_texture); |
165 | setFiltering(QSGTexture::Linear); |
166 | } |
167 | |
168 | ~TextureNode() override |
169 | { |
170 | delete m_texture; |
171 | } |
172 | |
173 | signals: |
174 | void textureInUse(); |
175 | void pendingNewTexture(); |
176 | |
177 | public slots: |
178 | |
179 | // This function gets called on the FBO rendering thread and will store the |
180 | // texture id and size and schedule an update on the window. |
181 | void newTexture(int id, const QSize &size) { |
182 | m_mutex.lock(); |
183 | m_id = id; |
184 | m_size = size; |
185 | m_mutex.unlock(); |
186 | |
187 | // We cannot call QQuickWindow::update directly here, as this is only allowed |
188 | // from the rendering thread or GUI thread. |
189 | emit pendingNewTexture(); |
190 | } |
191 | |
192 | |
193 | // Before the scene graph starts to render, we update to the pending texture |
194 | void prepareNode() { |
195 | m_mutex.lock(); |
196 | int newId = m_id; |
197 | QSize size = m_size; |
198 | m_id = 0; |
199 | m_mutex.unlock(); |
200 | if (newId) { |
201 | delete m_texture; |
202 | // note: include QQuickWindow::TextureHasAlphaChannel if the rendered content |
203 | // has alpha. |
204 | m_texture = m_window->createTextureFromId(id: newId, size); |
205 | setTexture(m_texture); |
206 | |
207 | markDirty(bits: DirtyMaterial); |
208 | |
209 | // This will notify the rendering thread that the texture is now being rendered |
210 | // and it can start rendering to the other one. |
211 | emit textureInUse(); |
212 | } |
213 | } |
214 | |
215 | private: |
216 | |
217 | int m_id; |
218 | QSize m_size; |
219 | |
220 | QMutex m_mutex; |
221 | |
222 | QSGTexture *m_texture; |
223 | QQuickWindow *m_window; |
224 | }; |
225 | |
226 | ThreadRenderer::ThreadRenderer() |
227 | : m_renderThread(nullptr) |
228 | { |
229 | setFlag(flag: ItemHasContents, enabled: true); |
230 | m_renderThread = new RenderThread(QSize(512, 512)); |
231 | } |
232 | |
233 | void ThreadRenderer::ready() |
234 | { |
235 | m_renderThread->surface = new QOffscreenSurface(); |
236 | m_renderThread->surface->setFormat(m_renderThread->context->format()); |
237 | m_renderThread->surface->create(); |
238 | |
239 | m_renderThread->moveToThread(thread: m_renderThread); |
240 | |
241 | connect(sender: window(), signal: &QQuickWindow::sceneGraphInvalidated, receiver: m_renderThread, slot: &RenderThread::shutDown, type: Qt::QueuedConnection); |
242 | |
243 | m_renderThread->start(); |
244 | update(); |
245 | } |
246 | |
247 | QSGNode *ThreadRenderer::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) |
248 | { |
249 | TextureNode *node = static_cast<TextureNode *>(oldNode); |
250 | |
251 | if (!m_renderThread->context) { |
252 | QOpenGLContext *current = window()->openglContext(); |
253 | // Some GL implementations requres that the currently bound context is |
254 | // made non-current before we set up sharing, so we doneCurrent here |
255 | // and makeCurrent down below while setting up our own context. |
256 | current->doneCurrent(); |
257 | |
258 | m_renderThread->context = new QOpenGLContext(); |
259 | m_renderThread->context->setFormat(current->format()); |
260 | m_renderThread->context->setShareContext(current); |
261 | m_renderThread->context->create(); |
262 | m_renderThread->context->moveToThread(thread: m_renderThread); |
263 | |
264 | current->makeCurrent(surface: window()); |
265 | |
266 | QMetaObject::invokeMethod(obj: this, member: "ready" ); |
267 | return nullptr; |
268 | } |
269 | |
270 | if (!node) { |
271 | node = new TextureNode(window()); |
272 | |
273 | /* Set up connections to get the production of FBO textures in sync with vsync on the |
274 | * rendering thread. |
275 | * |
276 | * When a new texture is ready on the rendering thread, we use a direct connection to |
277 | * the texture node to let it know a new texture can be used. The node will then |
278 | * emit pendingNewTexture which we bind to QQuickWindow::update to schedule a redraw. |
279 | * |
280 | * When the scene graph starts rendering the next frame, the prepareNode() function |
281 | * is used to update the node with the new texture. Once it completes, it emits |
282 | * textureInUse() which we connect to the FBO rendering thread's renderNext() to have |
283 | * it start producing content into its current "back buffer". |
284 | * |
285 | * This FBO rendering pipeline is throttled by vsync on the scene graph rendering thread. |
286 | */ |
287 | connect(sender: m_renderThread, signal: &RenderThread::textureReady, receiver: node, slot: &TextureNode::newTexture, type: Qt::DirectConnection); |
288 | connect(sender: node, signal: &TextureNode::pendingNewTexture, receiver: window(), slot: &QQuickWindow::update, type: Qt::QueuedConnection); |
289 | connect(sender: window(), signal: &QQuickWindow::beforeRendering, receiver: node, slot: &TextureNode::prepareNode, type: Qt::DirectConnection); |
290 | connect(sender: node, signal: &TextureNode::textureInUse, receiver: m_renderThread, slot: &RenderThread::renderNext, type: Qt::QueuedConnection); |
291 | |
292 | // Get the production of FBO textures started.. |
293 | QMetaObject::invokeMethod(obj: m_renderThread, member: "renderNext" , type: Qt::QueuedConnection); |
294 | } |
295 | |
296 | node->setRect(boundingRect()); |
297 | |
298 | return node; |
299 | } |
300 | |
301 | #include "threadrenderer.moc" |
302 | |