Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the QtCanvas3D module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPLv3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or later as published by the Free
** Software Foundation and appearing in the file LICENSE.GPL included in
** the packaging of this file. Please review the following information to
** ensure the GNU General Public License version 2.0 requirements will be
** met: http://www.gnu.org/licenses/gpl-2.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "canvas3d_p.h"
#include "context3d_p.h"
#include "typedarray_p.h"
#include "uint32array_p.h"
#include "arraybuffer_p.h"
#include "canvas3dcommon_p.h"
#include "canvasrendernode_p.h"
#include "teximage3d_p.h"
#include <QtGui/QGuiApplication>
#include <QtGui/QOffscreenSurface>
#include <QtGui/QOpenGLContext>
#include <QtGui/QOpenGLFramebufferObject>
#include <QtQml/QQmlEngine>
#include <QtQml/QQmlContext>
static QList<const QQuickWindow *> staticClearList;
static QHash<Canvas *, QQuickWindow *> canvasWindowList;
static QHash<QQuickWindow *, bool> windowClearList;
/*!
* \qmltype Canvas3D
* \since QtCanvas3D 1.0
* \ingroup qtcanvas3d-qml-types
* \brief Canvas that provides a 3D rendering context.
*
* The Canvas3D is a QML element that, when placed in your Qt Quick 2 scene, allows you to
* get a 3D rendering context and call 3D rendering API calls through that context object.
* Use of the rendering API requires knowledge of OpenGL like rendering APIs.
*
* There are two functions that are called by the Canvas3D implementation:
* \list
* \li initGL is emitted before the first frame is rendered, and usually during that you get
* the 3D context and initialize resources to be used later on during the rendering cycle.
* \li renderGL is emitted for each frame to be rendered, and usually during that you
* submit 3D rendering calls to draw whatever 3D content you want to be displayed.
* \endlist
*
* \sa Context3D, {QML Canvas 3D QML Types}
*/
/*!
* \internal
*/
Canvas::Canvas(QQuickItem *parent):
QQuickItem(parent),
m_renderNodeReady(false),
m_logAllCalls(false),
m_logAllErrors(true),
m_mainThread(QThread::currentThread()),
m_contextThread(0),
m_context3D(0),
m_isFirstRender(true),
m_glContext(0),
m_glContextQt(0),
m_contextWindow(0),
m_maxSamples(0),
m_devicePixelRatio(1.0f),
m_isContextAttribsSet(false),
m_antialiasFbo(0),
m_renderFbo(0),
m_displayFbo(0)
{
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__;
connect(this, &QQuickItem::windowChanged, this, &Canvas::handleWindowChanged);
connect(this, &Canvas::needRender, this, &Canvas::renderNext, Qt::QueuedConnection);
setAntialiasing(false);
// Set contents to false in case we are in qml designer to make component look nice
m_runningInDesigner = QGuiApplication::applicationDisplayName() == "Qml2Puppet";
setFlag(ItemHasContents, !m_runningInDesigner);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
if (QCoreApplication::testAttribute(Qt::AA_UseSoftwareOpenGL))
m_isSoftwareRendered = true;
#endif
/*!
* \qmlsignal void Canvas::initGL()
* Emitted once when Canvas3D is ready and OpenGL state initialization can be done by the client.
*/
/*!
* \qmlsignal void Canvas::renderGL()
* Emitted each time a new frame should be drawn to Canvas3D. Driven by the QML scenegraph loop.
*/
/*!
* \internal
*/
Canvas::~Canvas()
{
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__;
shutDown();
}
/*!
* \internal
*/
void Canvas::shutDown()
{
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__;
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
if (!m_glContext)
return;
disconnect(m_contextWindow, 0, this, 0);
disconnect(this, 0, this, 0);
m_glContext->makeCurrent(m_offscreenSurface);
delete m_renderFbo;
delete m_displayFbo;
delete m_antialiasFbo;
delete m_context3D;
m_glContext->doneCurrent();
if (m_logAllCalls) qDebug() << m_contextThread << m_mainThread;
if (m_contextThread && m_contextThread != m_mainThread)
m_glContext->deleteLater();
else
delete m_glContext;
m_glContext = 0;
// schedule this to be deleted only after we're done cleaning up
m_offscreenSurface->deleteLater();
m_glContextQt = 0;
}
/*!
* \qmlproperty bool Canvas3D::logAllCalls
* Specifies if all Canvas3D method calls (including internal ones) are logged to the console.
* Defaults to \c{false}.
*/
void Canvas::setLogAllCalls(bool logCalls)
{
if (m_logAllCalls != logCalls) {
m_logAllCalls = logCalls;
emit logAllCallsChanged(logCalls);
}
}
bool Canvas::logAllCalls() const
{
return m_logAllCalls;
}
/*!
* \qmlproperty bool Canvas3D::logAllErrors
* Specifies if all Canvas3D errors are logged to the console. Defaults to \c{true}.
*/
void Canvas::setLogAllErrors(bool logErrors)
{
if (m_logAllErrors != logErrors) {
m_logAllErrors = logErrors;
emit logAllErrorsChanged(logErrors);
}
}
bool Canvas::logAllErrors() const
{
return m_logAllErrors;
}
/*!
* \qmlsignal Canvas3D::needRender()
* This signal, if emitted, causes a re-rendering cycle to happen. Usually this is needed
* if a value that affects the look of the 3D scene has changed, but no other mechanism
* triggers the re-render cycle.
*/
/*!
* \fn Canvas::needRender()
* \internal
*/
/*!
* \qmlsignal Canvas3D::textureReady(int id, size size, float devicePixelRatio)
* Emitted when a new texture is ready to inform the render node.
*/
/*!
* \fn Canvas::textureReady(int id, const QSize &size, float devicePixelRatio)
* \internal
*/
/*!
* \qmlproperty float Canvas3D::devicePixelRatio
* Specifies the ratio between logical pixels (used by the Qt Quick) and actual physical
* on-screen pixels (used by the 3D rendering).
*/
float Canvas::devicePixelRatio()
{
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__;
QQuickWindow *win = window();
if (win)
return win->devicePixelRatio();
else
return 1.0f;
}
/*!
* \qmlmethod Context3D Canvas3D::getContext(string type)
* Returns the 3D rendering context that allows 3D rendering calls to be made.
* The \a type parameter is ignored for now, but a string is expected to be given.
*/
/*!
* \internal
*/
CanvasContext *Canvas::getContext(const QString &type)
{
QVariantMap map;
return getContext(type, map);
}
/*!
* \qmlmethod Context3D Canvas3D::getContext(string type, ContextAttributes options)
* Returns the 3D rendering context that allows 3D rendering calls to be made.
* The \a type parameter is ignored for now, but a string is expected to be given.
* The \a options parameter is only parsed when the first call to getContext() is
* made and is ignored in subsequent calls if given. If the first call is made without
* giving the \a options parameter, then the context and render target is initialized with
* default configuration.
*
* \sa ContextAttributes, Context3D
*/
/*!
* \internal
*/
CanvasContext *Canvas::getContext(const QString &type, const QVariantMap &options)
{
Q_UNUSED(type);
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << "(" << type << ", " << options << ")";
if (!m_isContextAttribsSet) {
m_isContextAttribsSet = true;
m_contextAttribs.setFrom(options);
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << " Attribs:" << m_contextAttribs;
// If we can't do antialiasing, ensure we don't even try to enable it
if (m_maxSamples == 0)
m_contextAttribs.setAntialias(false);
// Ensure ignored attributes are left to their default state
m_contextAttribs.setAlpha(false);
m_contextAttribs.setPremultipliedAlpha(false);
m_contextAttribs.setPreserveDrawingBuffer(false);
m_contextAttribs.setPreferLowPowerToHighPerformance(false);
m_contextAttribs.setFailIfMajorPerformanceCaveat(false);
}
if (!m_context3D) {
updateWindowParameters();
// Initialize the swap buffer chain
QOpenGLFramebufferObjectFormat format;
if (m_contextAttribs.depth() && m_contextAttribs.stencil() && !m_contextAttribs.antialias())
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
else if (m_contextAttribs.depth() && !m_contextAttribs.antialias())
format.setAttachment(QOpenGLFramebufferObject::Depth);
else if (m_contextAttribs.stencil() && !m_contextAttribs.antialias())
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
else
format.setAttachment(QOpenGLFramebufferObject::NoAttachment);
QOpenGLFramebufferObjectFormat antialiasFboFormat;
if (m_contextAttribs.antialias()) {
antialiasFboFormat.setSamples(m_maxSamples);
if (m_contextAttribs.depth() && m_contextAttribs.stencil())
antialiasFboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
else if (m_contextAttribs.depth())
antialiasFboFormat.setAttachment(QOpenGLFramebufferObject::Depth);
else if (m_contextAttribs.stencil())
antialiasFboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
else
antialiasFboFormat.setAttachment(QOpenGLFramebufferObject::NoAttachment);
}
QSurfaceFormat surfaceFormat = m_glContextQt->format();
if (m_isOpenGLES2) {
surfaceFormat.setMajorVersion(2);
surfaceFormat.setMinorVersion(0);
} else {
surfaceFormat.setSwapBehavior(QSurfaceFormat::SingleBuffer);
surfaceFormat.setSwapInterval(0);
}
if (m_contextAttribs.antialias() && !m_isSoftwareRendered)
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
surfaceFormat.setSamples(m_maxSamples);
else
surfaceFormat.setSamples(0);
if (m_contextAttribs.alpha())
surfaceFormat.setAlphaBufferSize(8);
else
surfaceFormat.setAlphaBufferSize(0);
if (m_contextAttribs.depth())
surfaceFormat.setDepthBufferSize(24);
// Ensure
if (m_contextAttribs.stencil())
surfaceFormat.setStencilBufferSize(8);
else
surfaceFormat.setStencilBufferSize(-1);
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << " QOpenGLContext with surfaceFormat :" << surfaceFormat;
m_contextWindow = window();
m_contextThread = QThread::currentThread();
m_glContext = new QOpenGLContext();
m_glContext->setFormat(surfaceFormat);
m_glContext->setShareContext(m_glContextQt);
m_glContext->create();
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << " QOffscreenSurface with surfaceFormat :" << surfaceFormat;
m_offscreenSurface = new QOffscreenSurface();
m_offscreenSurface->setFormat(m_glContext->format());
m_offscreenSurface->create();
m_glContext->makeCurrent(m_offscreenSurface);
// Initialize OpenGL functions using the created GL context
initializeOpenGLFunctions();
// Create the FBOs
QOpenGLFramebufferObjectFormat fmt = format;
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << " Creating FBO's with attachment format of :" << fmt.attachment();
m_displayFbo = new QOpenGLFramebufferObject(m_initialisedSize, fmt);
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << " m_displayFbo handle:" << m_displayFbo->handle() << " isValid" << m_displayFbo->isValid();
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << " Creating FBO's with attachment format of :" << format.attachment();
m_renderFbo = new QOpenGLFramebufferObject(m_initialisedSize, format);
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << " m_renderFbo handle:" << m_renderFbo->handle() << " isValid" << m_renderFbo->isValid();
m_renderFbo->bind();
if (m_contextAttribs.antialias()) {
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << " Creating MSAA buffer with samples:" << antialiasFboFormat.samples() << "and attachment format of :" << antialiasFboFormat.attachment();
m_antialiasFbo = new QOpenGLFramebufferObject(m_initialisedSize, antialiasFboFormat);
}
m_context3D = new CanvasContext(m_glContext, m_offscreenSurface,
m_initialisedSize.width() * m_devicePixelRatio,
m_initialisedSize.height() * m_devicePixelRatio,
m_isOpenGLES2);
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
m_context3D->setCanvas(this);
m_context3D->setDevicePixelRatio(m_devicePixelRatio);
m_context3D->setContextAttributes(m_contextAttribs);
emit contextChanged(m_context3D);
}
return m_context3D;
}
/*!
* \internal
*/
GLuint Canvas::drawFBOHandle()
{
GLuint fbo = 0;
if (m_renderFbo)
fbo = m_renderFbo->handle();
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << "():" << fbo;
return fbo;
}
/*!
* \internal
*/
void Canvas::handleWindowChanged(QQuickWindow *window)
{
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << "(" << window << ")";
if (!window)
return;
}
/*!
* \internal
*/
void Canvas::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
{
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__;
QQuickItem::geometryChanged(newGeometry, oldGeometry);
m_cachedGeometry = newGeometry;
}
/*!
* \internal
*/
void Canvas::itemChange(ItemChange change, const ItemChangeData &value)
{
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << change;
QQuickItem::itemChange(change, value);
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
}
/*!
* \internal
*/
CanvasContext *Canvas::context()
{
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << "()";
return m_context3D;
}
/*!
* \internal
*/
void Canvas::updateWindowParameters()
{
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__;
// Update the device pixel ratio, window size and bounding box
QQuickWindow *win = window();
if (win) {
qreal pixelRatio = win->devicePixelRatio();
if (pixelRatio != m_devicePixelRatio) {
m_devicePixelRatio = pixelRatio;
emit devicePixelRatioChanged(pixelRatio);
win->update();
}
}
if (m_context3D) {
if (m_context3D->devicePixelRatio() != m_devicePixelRatio)
m_context3D->setDevicePixelRatio(m_devicePixelRatio);
}
}
/*!
* \internal
*/
void Canvas::ready()
{
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__;
connect(window(), &QQuickWindow::sceneGraphInvalidated,
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
update();
}
/*!
* \internal
*/
QSGNode *Canvas::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
{
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << "("<<oldNode<<", " << data << ")";
updateWindowParameters();
m_initialisedSize = boundingRect().size().toSize() * m_devicePixelRatio;
if (m_logAllCalls) qDebug() << " m_initialisedSize:" << m_initialisedSize << "devicePixelRatio:" << m_devicePixelRatio;
if (m_runningInDesigner
|| m_initialisedSize.width() <= 0
|| m_initialisedSize.height() <= 0
|| !window()) {
delete oldNode;
if (m_logAllCalls) qDebug() << " Returns null";
return 0;
}
CanvasRenderNode *node = static_cast<CanvasRenderNode *>(oldNode);
if (!m_glContextQt) {
m_glContextQt = window()->openglContext();
m_isOpenGLES2 = m_glContextQt->isOpenGLES();
if (!m_isOpenGLES2)
m_maxSamples = 4;
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
return 0;
}
if (!node) {
node = new CanvasRenderNode(this, window());
/* Set up connections to get the production of FBO textures in sync with vsync on the
* main thread.
*
* When a new texture is ready on the rendering thread, we use a direct connection to
* the texture node to let it know a new texture can be used. The node will then
* emit pendingNewTexture which we bind to QQuickWindow::update to schedule a redraw.
*
* When the scene graph starts rendering the next frame, the prepareNode() function
* is used to update the node with the new texture. Once it completes, it emits
* textureInUse() which we connect to the FBO rendering thread's renderNext() to have
* it start producing content into its current "back buffer".
*
* This FBO rendering pipeline is throttled by vsync on the scene graph rendering thread.
*/
connect(this, &Canvas::textureReady,
node, &CanvasRenderNode::newTexture,
Qt::DirectConnection);
connect(node, &CanvasRenderNode::pendingNewTexture,
window(), &QQuickWindow::update,
Qt::QueuedConnection);
connect(window(), &QQuickWindow::beforeRendering,
node, &CanvasRenderNode::prepareNode,
Qt::DirectConnection);
connect(node, &CanvasRenderNode::textureInUse,
this, &Canvas::renderNext,
Qt::QueuedConnection);
// Get the production of FBO textures started..
update();
}
node->setRect(boundingRect());
m_renderNodeReady = true;
return node;
}
/*!
* \internal
*/
void Canvas::renderNext()
{
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__;
updateWindowParameters();
// Don't try to do anything before the render node has been created
if (!m_renderNodeReady) {
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << " Render node not ready, returning";
return;
}
if (!m_glContext) {
// Call the initialize function from QML/JavaScript until it calls the getContext() that in turn creates the buffers
// Allow the JavaScript code to call the getContext() to create the context object and FBOs
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
if (!m_isContextAttribsSet) {
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << " Context attributes not set, returning";
return;
}
if (!m_glContext) {
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << " GLContext3D not created, returning";
return;
}
}
// We have a GL context, make it current
m_glContext->makeCurrent(m_offscreenSurface);
// Bind the correct render target FBO
if (m_context3D->currentFramebuffer() == 0) {
if (m_contextAttribs.antialias())
m_antialiasFbo->bind();
else
m_renderFbo->bind();
} else {
glBindFramebuffer(GL_FRAMEBUFFER, m_context3D->currentFramebuffer());
}
// Ensure we have correct clip rect set in the context
QRect viewport = m_context3D->glViewportRect();
glViewport(viewport.x(), viewport.y(), viewport.width(), viewport.height());
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << " viewport set to " << viewport;
// Check that we're complete component before drawing
if (!isComponentComplete())
return;
// Check if any images are loaded and need to be notified while the correct
// GL context is current.
QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
CanvasTextureImageFactory::factory(engine)->notifyLoadedImages();
// Resolve MSAA
if (m_contextAttribs.antialias()) {
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << " Resolving MSAA";
QOpenGLFramebufferObject::blitFramebuffer(m_displayFbo, m_antialiasFbo);
}
// We need to flush the contents to the FBO before posting
// the texture to the other thread, otherwise, we might
// get unexpected results.
glFlush();
glFinish();
qSwap(m_renderFbo, m_displayFbo);
if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << " Displaying texture " << m_displayFbo->texture() << " from FBO: " << m_displayFbo->handle();
// Rebind default FBO
QOpenGLFramebufferObject::bindDefault();
m_glContext->doneCurrent();
// Notify the render node of new texture
emit textureReady(m_displayFbo->texture(), m_initialisedSize, m_devicePixelRatio);
}