• Kai Koehne's avatar
    Inspector: Do not assert when trying to stream QModelIndex · efa81f83
    Kai Koehne authored
    
    Some QVariant's like QModelIndex cannot be streamed in a meaningful way:
    QDataType::save() will return false for them. However, this leads to a
    qWarning and Q_ASSERT in QVariant::operator<<().
    
    To prevent this we're calling QDataType::save() manually beforehand. We
    however throw away the result if it succeeds, and still call
    QVariant::operator<<() to get the benefits of the QDataStream version
    handling.
    
    The alternatives would be to make QVariant::operator<<() not assert,
    or blacklist all known types with problems manually. Both seem to
    be difficult though ...
    
    Change-Id: I4f5fe6d5a3a076c24fbc73371a4d12d720de53da
    Task-number: QTBUG-42438
    Reviewed-by: default avatarUlf Hermann <ulf.hermann@theqtcompany.com>
    Reviewed-by: default avatarSimon Hausmann <simon.hausmann@digia.com>
    efa81f83
canvas3d.cpp 21.17 KiB
/****************************************************************************
**
** 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.
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
* \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), #if defined(QT_OPENGL_ES_2) m_maxSamples(0), #else m_maxSamples(4), #endif m_samples(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); } /*! * \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__;
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
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. */ /*!
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
* \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);
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
// 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 defined(QT_OPENGL_ES_2) surfaceFormat.setMajorVersion(2); surfaceFormat.setMinorVersion(0); #endif surfaceFormat.setSwapBehavior(QSurfaceFormat::SingleBuffer); surfaceFormat.setSwapInterval(0); if (m_contextAttribs.antialias()) 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);
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
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_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; emit needRender(); } /*! * \internal
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
*/ void Canvas::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__; QQuickItem::geometryChanged(newGeometry, oldGeometry); m_cachedGeometry = newGeometry; emit needRender(); } /*! * \internal */ void Canvas::itemChange(ItemChange change, const ItemChangeData &value) { if (m_logAllCalls) qDebug() << "Canvas3D::" << __FUNCTION__ << change; QQuickItem::itemChange(change, value); emit needRender(); } /*! * \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, this, &Canvas::shutDown); update(); } /*!
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
* \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(); ready(); 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.. emit needRender(); update(); } node->setRect(boundingRect()); emit needRender(); m_renderNodeReady = true; return node;
561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
} /*! * \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 emit initGL(); 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(); // Call render in QML JavaScript side emit renderGL(); // 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
631632633634635636637638639640641642643644645
// 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); }