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
69class RenderControl : public QQuickRenderControl
70{
71public:
72 RenderControl(QWindow *w) : m_window(w) { }
73 QWindow *renderWindow(QPoint *offset) override;
74
75private:
76 QWindow *m_window;
77};
78
79QWindow *RenderControl::renderWindow(QPoint *offset)
80{
81 if (offset)
82 *offset = QPoint(0, 0);
83 return m_window;
84}
85
86WindowSingleThreaded::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
151WindowSingleThreaded::~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
176void 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
191void WindowSingleThreaded::destroyFbo()
192{
193 delete m_fbo;
194 m_fbo = nullptr;
195}
196
197void 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
227void WindowSingleThreaded::requestUpdate()
228{
229 if (!m_updateTimer.isActive())
230 m_updateTimer.start();
231}
232
233void 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
274void 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
285void 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
294void 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
305void 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
319void 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
332void WindowSingleThreaded::handleScreenChange()
333{
334 if (m_dpr != devicePixelRatio())
335 resizeFbo();
336}
337
338void 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
348void 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

source code of qtdeclarative/examples/quick/rendercontrol/window_singlethreaded.cpp