An error occurred while loading the file. Please try again.
-
J-P Nurmi authored
Make Style inherit AbstractStyle, so it becomes the base class of all styles. AbstractStyle also now has a default "data" property so it's possible to conveniently declare helper content inside styles. Change-Id: Id84f3762cfae506e65021322619d7fb77b137994 Reviewed-by:
Tomasz Olszak <olszak.tomasz@gmail.com> Reviewed-by:
Jens Bache-Wiig <jens.bache-wiig@digia.com>
26dda49c
/****************************************************************************
**
** 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);
}