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 "window_singlethreaded.h" |
52 | #include "cuberenderer.h" |
53 | #include <QOpenGLContext> |
54 | #include <QOpenGLFunctions> |
55 | #include <QOpenGLFramebufferObject> |
56 | #include <QOpenGLShaderProgram> |
57 | #include <QOpenGLVertexArrayObject> |
58 | #include <QOpenGLBuffer> |
59 | #include <QOpenGLVertexArrayObject> |
60 | #include <QOffscreenSurface> |
61 | #include <QScreen> |
62 | #include <QQmlEngine> |
63 | #include <QQmlComponent> |
64 | #include <QQuickItem> |
65 | #include <QQuickWindow> |
66 | #include <QQuickRenderControl> |
67 | #include <QCoreApplication> |
68 | |
69 | class RenderControl : public QQuickRenderControl |
70 | { |
71 | public: |
72 | RenderControl(QWindow *w) : m_window(w) { } |
73 | QWindow *renderWindow(QPoint *offset) override; |
74 | |
75 | private: |
76 | QWindow *m_window; |
77 | }; |
78 | |
79 | QWindow *RenderControl::renderWindow(QPoint *offset) |
80 | { |
81 | if (offset) |
82 | *offset = QPoint(0, 0); |
83 | return m_window; |
84 | } |
85 | |
86 | WindowSingleThreaded::WindowSingleThreaded() |
87 | : m_rootItem(nullptr), |
88 | m_fbo(nullptr), |
89 | m_quickInitialized(false), |
90 | m_quickReady(false), |
91 | m_dpr(0) |
92 | { |
93 | setSurfaceType(QSurface::OpenGLSurface); |
94 | |
95 | // The rendercontrol does not necessarily need an FBO. Demonstrate this |
96 | // when requested. |
97 | m_onscreen = QCoreApplication::arguments().contains(QStringLiteral("--onscreen" )); |
98 | |
99 | QSurfaceFormat format; |
100 | // Qt Quick may need a depth and stencil buffer. Always make sure these are available. |
101 | format.setDepthBufferSize(16); |
102 | format.setStencilBufferSize(8); |
103 | setFormat(format); |
104 | |
105 | m_context = new QOpenGLContext; |
106 | m_context->setFormat(format); |
107 | m_context->create(); |
108 | |
109 | m_offscreenSurface = new QOffscreenSurface; |
110 | // Pass m_context->format(), not format. Format does not specify and color buffer |
111 | // sizes, while the context, that has just been created, reports a format that has |
112 | // these values filled in. Pass this to the offscreen surface to make sure it will be |
113 | // compatible with the context's configuration. |
114 | m_offscreenSurface->setFormat(m_context->format()); |
115 | m_offscreenSurface->create(); |
116 | |
117 | m_cubeRenderer = new CubeRenderer(m_offscreenSurface); |
118 | |
119 | m_renderControl = new RenderControl(this); |
120 | |
121 | // Create a QQuickWindow that is associated with out render control. Note that this |
122 | // window never gets created or shown, meaning that it will never get an underlying |
123 | // native (platform) window. |
124 | m_quickWindow = new QQuickWindow(m_renderControl); |
125 | |
126 | // Create a QML engine. |
127 | m_qmlEngine = new QQmlEngine; |
128 | if (!m_qmlEngine->incubationController()) |
129 | m_qmlEngine->setIncubationController(m_quickWindow->incubationController()); |
130 | |
131 | // When Quick says there is a need to render, we will not render immediately. Instead, |
132 | // a timer with a small interval is used to get better performance. |
133 | m_updateTimer.setSingleShot(true); |
134 | m_updateTimer.setInterval(5); |
135 | connect(sender: &m_updateTimer, signal: &QTimer::timeout, receiver: this, slot: &WindowSingleThreaded::render); |
136 | |
137 | // Now hook up the signals. For simplicy we don't differentiate between |
138 | // renderRequested (only render is needed, no sync) and sceneChanged (polish and sync |
139 | // is needed too). |
140 | connect(sender: m_quickWindow, signal: &QQuickWindow::sceneGraphInitialized, receiver: this, slot: &WindowSingleThreaded::createFbo); |
141 | connect(sender: m_quickWindow, signal: &QQuickWindow::sceneGraphInvalidated, receiver: this, slot: &WindowSingleThreaded::destroyFbo); |
142 | connect(sender: m_renderControl, signal: &QQuickRenderControl::renderRequested, receiver: this, slot: &WindowSingleThreaded::requestUpdate); |
143 | connect(sender: m_renderControl, signal: &QQuickRenderControl::sceneChanged, receiver: this, slot: &WindowSingleThreaded::requestUpdate); |
144 | |
145 | // Just recreating the FBO on resize is not sufficient, when moving between screens |
146 | // with different devicePixelRatio the QWindow size may remain the same but the FBO |
147 | // dimension is to change regardless. |
148 | connect(sender: this, signal: &QWindow::screenChanged, receiver: this, slot: &WindowSingleThreaded::handleScreenChange); |
149 | } |
150 | |
151 | WindowSingleThreaded::~WindowSingleThreaded() |
152 | { |
153 | // Make sure the context is current while doing cleanup. Note that we use the |
154 | // offscreen surface here because passing 'this' at this point is not safe: the |
155 | // underlying platform window may already be destroyed. To avoid all the trouble, use |
156 | // another surface that is valid for sure. |
157 | m_context->makeCurrent(surface: m_offscreenSurface); |
158 | |
159 | // Delete the render control first since it will free the scenegraph resources. |
160 | // Destroy the QQuickWindow only afterwards. |
161 | delete m_renderControl; |
162 | |
163 | delete m_qmlComponent; |
164 | delete m_quickWindow; |
165 | delete m_qmlEngine; |
166 | delete m_fbo; |
167 | |
168 | m_context->doneCurrent(); |
169 | |
170 | delete m_cubeRenderer; |
171 | |
172 | delete m_offscreenSurface; |
173 | delete m_context; |
174 | } |
175 | |
176 | void WindowSingleThreaded::createFbo() |
177 | { |
178 | // The scene graph has been initialized. It is now time to create an FBO and associate |
179 | // it with the QQuickWindow. |
180 | m_dpr = devicePixelRatio(); |
181 | if (!m_onscreen) { |
182 | m_fbo = new QOpenGLFramebufferObject(size() * m_dpr, QOpenGLFramebufferObject::CombinedDepthStencil); |
183 | m_quickWindow->setRenderTarget(m_fbo); |
184 | } else { |
185 | // Special case: No FBO. Render directly to the window's default framebuffer. |
186 | m_onscreenSize = size() * m_dpr; |
187 | m_quickWindow->setRenderTarget(fboId: 0, size: m_onscreenSize); |
188 | } |
189 | } |
190 | |
191 | void WindowSingleThreaded::destroyFbo() |
192 | { |
193 | delete m_fbo; |
194 | m_fbo = nullptr; |
195 | } |
196 | |
197 | void WindowSingleThreaded::render() |
198 | { |
199 | QSurface *surface = m_offscreenSurface; |
200 | if (m_onscreen) |
201 | surface = this; |
202 | if (!m_context->makeCurrent(surface)) |
203 | return; |
204 | |
205 | // Polish, synchronize and render the next frame (into our fbo). In this example |
206 | // everything happens on the same thread and therefore all three steps are performed |
207 | // in succession from here. In a threaded setup the render() call would happen on a |
208 | // separate thread. |
209 | m_renderControl->polishItems(); |
210 | m_renderControl->sync(); |
211 | m_renderControl->render(); |
212 | |
213 | m_quickWindow->resetOpenGLState(); |
214 | QOpenGLFramebufferObject::bindDefault(); |
215 | |
216 | m_context->functions()->glFlush(); |
217 | |
218 | m_quickReady = true; |
219 | |
220 | // Get something onto the screen. |
221 | if (!m_onscreen) |
222 | m_cubeRenderer->render(w: this, share: m_context, texture: m_quickReady ? m_fbo->texture() : 0); |
223 | else |
224 | m_context->swapBuffers(surface: this); |
225 | } |
226 | |
227 | void WindowSingleThreaded::requestUpdate() |
228 | { |
229 | if (!m_updateTimer.isActive()) |
230 | m_updateTimer.start(); |
231 | } |
232 | |
233 | void WindowSingleThreaded::run() |
234 | { |
235 | disconnect(sender: m_qmlComponent, signal: &QQmlComponent::statusChanged, receiver: this, slot: &WindowSingleThreaded::run); |
236 | |
237 | if (m_qmlComponent->isError()) { |
238 | const QList<QQmlError> errorList = m_qmlComponent->errors(); |
239 | for (const QQmlError &error : errorList) |
240 | qWarning() << error.url() << error.line() << error; |
241 | return; |
242 | } |
243 | |
244 | QObject *rootObject = m_qmlComponent->create(); |
245 | if (m_qmlComponent->isError()) { |
246 | const QList<QQmlError> errorList = m_qmlComponent->errors(); |
247 | for (const QQmlError &error : errorList) |
248 | qWarning() << error.url() << error.line() << error; |
249 | return; |
250 | } |
251 | |
252 | m_rootItem = qobject_cast<QQuickItem *>(object: rootObject); |
253 | if (!m_rootItem) { |
254 | qWarning(msg: "run: Not a QQuickItem" ); |
255 | delete rootObject; |
256 | return; |
257 | } |
258 | |
259 | // The root item is ready. Associate it with the window. |
260 | m_rootItem->setParentItem(m_quickWindow->contentItem()); |
261 | |
262 | // Update item and rendering related geometries. |
263 | updateSizes(); |
264 | |
265 | // Initialize the render control and our OpenGL resources. |
266 | QSurface *surface = m_offscreenSurface; |
267 | if (m_onscreen) |
268 | surface = this; |
269 | m_context->makeCurrent(surface); |
270 | m_renderControl->initialize(gl: m_context); |
271 | m_quickInitialized = true; |
272 | } |
273 | |
274 | void WindowSingleThreaded::updateSizes() |
275 | { |
276 | // Behave like SizeRootObjectToView. |
277 | m_rootItem->setWidth(width()); |
278 | m_rootItem->setHeight(height()); |
279 | |
280 | m_quickWindow->setGeometry(posx: 0, posy: 0, w: width(), h: height()); |
281 | |
282 | m_cubeRenderer->resize(w: width(), h: height()); |
283 | } |
284 | |
285 | void WindowSingleThreaded::startQuick(const QString &filename) |
286 | { |
287 | m_qmlComponent = new QQmlComponent(m_qmlEngine, QUrl(filename)); |
288 | if (m_qmlComponent->isLoading()) |
289 | connect(sender: m_qmlComponent, signal: &QQmlComponent::statusChanged, receiver: this, slot: &WindowSingleThreaded::run); |
290 | else |
291 | run(); |
292 | } |
293 | |
294 | void WindowSingleThreaded::exposeEvent(QExposeEvent *) |
295 | { |
296 | if (isExposed()) { |
297 | if (!m_quickInitialized) { |
298 | if (!m_onscreen) |
299 | m_cubeRenderer->render(w: this, share: m_context, texture: m_quickReady ? m_fbo->texture() : 0); |
300 | startQuick(QStringLiteral("qrc:/rendercontrol/demo.qml" )); |
301 | } |
302 | } |
303 | } |
304 | |
305 | void WindowSingleThreaded::resizeFbo() |
306 | { |
307 | QSurface *surface = m_offscreenSurface; |
308 | if (m_onscreen) |
309 | surface = this; |
310 | if (m_rootItem && m_context->makeCurrent(surface)) { |
311 | delete m_fbo; |
312 | createFbo(); |
313 | m_context->doneCurrent(); |
314 | updateSizes(); |
315 | render(); |
316 | } |
317 | } |
318 | |
319 | void WindowSingleThreaded::resizeEvent(QResizeEvent *) |
320 | { |
321 | // If this is a resize after the scene is up and running, recreate the fbo and the |
322 | // Quick item and scene. |
323 | if (!m_onscreen) { |
324 | if (m_fbo && m_fbo->size() != size() * devicePixelRatio()) |
325 | resizeFbo(); |
326 | } else { |
327 | if (m_onscreenSize != size() * devicePixelRatio()) |
328 | resizeFbo(); |
329 | } |
330 | } |
331 | |
332 | void WindowSingleThreaded::handleScreenChange() |
333 | { |
334 | if (m_dpr != devicePixelRatio()) |
335 | resizeFbo(); |
336 | } |
337 | |
338 | void WindowSingleThreaded::mousePressEvent(QMouseEvent *e) |
339 | { |
340 | // Use the constructor taking localPos and screenPos. That puts localPos into the |
341 | // event's localPos and windowPos, and screenPos into the event's screenPos. This way |
342 | // the windowPos in e is ignored and is replaced by localPos. This is necessary |
343 | // because QQuickWindow thinks of itself as a top-level window always. |
344 | QMouseEvent mappedEvent(e->type(), e->localPos(), e->screenPos(), e->button(), e->buttons(), e->modifiers()); |
345 | QCoreApplication::sendEvent(receiver: m_quickWindow, event: &mappedEvent); |
346 | } |
347 | |
348 | void WindowSingleThreaded::mouseReleaseEvent(QMouseEvent *e) |
349 | { |
350 | QMouseEvent mappedEvent(e->type(), e->localPos(), e->screenPos(), e->button(), e->buttons(), e->modifiers()); |
351 | QCoreApplication::sendEvent(receiver: m_quickWindow, event: &mappedEvent); |
352 | } |
353 | |