diff --git a/examples/canvas3d/canvas3d/framebuffer/main.cpp b/examples/canvas3d/canvas3d/framebuffer/main.cpp index dc9ef5c31f8c9a64201eada89be5316196b30c39..b516e40364ebc4aa56d0a9d8f137325a36d23722 100644 --- a/examples/canvas3d/canvas3d/framebuffer/main.cpp +++ b/examples/canvas3d/canvas3d/framebuffer/main.cpp @@ -59,6 +59,7 @@ int main(int argc, char *argv[]) viewer.setTitle(QStringLiteral("Render into FrameBuffer")); viewer.setResizeMode(QQuickView::SizeRootObjectToView); + viewer.setColor("#f2f2f2"); viewer.show(); return app.exec(); diff --git a/examples/canvas3d/canvas3d/framebuffer/qml/framebuffer/framebuffer.js b/examples/canvas3d/canvas3d/framebuffer/qml/framebuffer/framebuffer.js index f268682a3d5b2a15111d05de96a77dd90f6e015d..397cca2d720a37c080e090e26e9d3459274fdb41 100644 --- a/examples/canvas3d/canvas3d/framebuffer/qml/framebuffer/framebuffer.js +++ b/examples/canvas3d/canvas3d/framebuffer/qml/framebuffer/framebuffer.js @@ -75,7 +75,7 @@ function initializeGL(canvas, textureLoader) { canvas3d = canvas try { // Get the OpenGL context object that represents the API we call - gl = canvas.getContext("canvas3d", {depth:true, antialias:true}); + gl = canvas.getContext("canvas3d", {depth:true, antialias:true, alpha:false}); // Setup the OpenGL state gl.enable(gl.DEPTH_TEST); diff --git a/examples/canvas3d/canvas3d/framebuffer/qml/framebuffer/main.qml b/examples/canvas3d/canvas3d/framebuffer/qml/framebuffer/main.qml index ba23f263d99def3033cb2f3ed536d5a77d23e3c5..68798cdf4e30169375eccbafd7e370875604b208 100644 --- a/examples/canvas3d/canvas3d/framebuffer/qml/framebuffer/main.qml +++ b/examples/canvas3d/canvas3d/framebuffer/qml/framebuffer/main.qml @@ -41,12 +41,11 @@ import QtQuick.Layouts 1.0 import "framebuffer.js" as GLCode -Rectangle { +Item { id: mainview width: 1280 height: 768 visible: true - color: "#f2f2f2" Canvas3D { id: canvas3d diff --git a/examples/canvas3d/canvas3d/interaction/main.cpp b/examples/canvas3d/canvas3d/interaction/main.cpp index 05e00a0ca6be86cb753c1326e6c043d30b5c8d32..91da80e48081b291098b53ebd4a7a4cdf8cc5852 100644 --- a/examples/canvas3d/canvas3d/interaction/main.cpp +++ b/examples/canvas3d/canvas3d/interaction/main.cpp @@ -59,6 +59,7 @@ int main(int argc, char *argv[]) viewer.setTitle(QStringLiteral("Interaction")); viewer.setResizeMode(QQuickView::SizeRootObjectToView); + viewer.setColor("#fafafa"); viewer.show(); return app.exec(); diff --git a/examples/canvas3d/canvas3d/interaction/qml/interaction/interaction.js b/examples/canvas3d/canvas3d/interaction/qml/interaction/interaction.js index e8ca513ebce13f1347f57fde074f6d27043ca702..11ff0796e67e86ff5036305627384d0fde4d5257 100644 --- a/examples/canvas3d/canvas3d/interaction/qml/interaction/interaction.js +++ b/examples/canvas3d/canvas3d/interaction/qml/interaction/interaction.js @@ -86,7 +86,7 @@ function initializeGL(canvas) { // Get the OpenGL context jsonObj that represents the API we call log("Getting Context"); - gl = canvas.getContext("canvas3d", {depth:true, antialias:true}); + gl = canvas.getContext("canvas3d", {depth:true, antialias:true, alpha:false}); log("Context received "+gl); var contextConfig = gl.getContextAttributes(); diff --git a/examples/canvas3d/canvas3d/interaction/qml/interaction/main.qml b/examples/canvas3d/canvas3d/interaction/qml/interaction/main.qml index 1836c47f481eb654bea8f62d11c3cf4544684da4..88e9b31d799c4a95e258496d6ce28ebb099174e2 100644 --- a/examples/canvas3d/canvas3d/interaction/qml/interaction/main.qml +++ b/examples/canvas3d/canvas3d/interaction/qml/interaction/main.qml @@ -43,12 +43,11 @@ import QtQuick.Layouts 1.0 import "interaction.js" as GLCode -Rectangle { +Item { id: mainview width: 1280 height: 768 visible: true - color: "#fafafa" Canvas3D { id: canvas3d diff --git a/examples/canvas3d/canvas3d/jsonmodels/qml/jsonmodels/jsonmodels.js b/examples/canvas3d/canvas3d/jsonmodels/qml/jsonmodels/jsonmodels.js index 8a6dafe6c899bfe8caab496871581c355ba81770..2727cb82b58a3241f3def814a0b8e71634d8890a 100644 --- a/examples/canvas3d/canvas3d/jsonmodels/qml/jsonmodels/jsonmodels.js +++ b/examples/canvas3d/canvas3d/jsonmodels/qml/jsonmodels/jsonmodels.js @@ -117,7 +117,7 @@ function initializeGL(canvas) { canvas3d = canvas log("initializeGL...") try { - gl = canvas.getContext("canvas3d", {depth:true, antialias:true}); + gl = canvas.getContext("canvas3d", {depth:true, antialias:true, alpha:false}); log(" Received context: "+gl); stateDumpExt = gl.getExtension("QTCANVAS3D_gl_state_dump"); diff --git a/examples/canvas3d/canvas3d/quickitemtexture/qml/quickitemtexture/main.qml b/examples/canvas3d/canvas3d/quickitemtexture/qml/quickitemtexture/main.qml index 3c16683e7612754df47ac21cbe1e2cefea18ce5e..433e226075e8b85eeba23ac9d055259e515a53fb 100644 --- a/examples/canvas3d/canvas3d/quickitemtexture/qml/quickitemtexture/main.qml +++ b/examples/canvas3d/canvas3d/quickitemtexture/qml/quickitemtexture/main.qml @@ -72,7 +72,7 @@ Window { + "Z Rot:" + (canvas3d.zRotAnim | 0) + "\n" + "FPS:" + canvas3d.fps color: "red" - font.pointSize: 30 + font.pointSize: 26 horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter } diff --git a/examples/canvas3d/canvas3d/textureandlight/main.cpp b/examples/canvas3d/canvas3d/textureandlight/main.cpp index d2472a242150756312c3c8a40bd43444e5ab3fde..9dfb4bfe14480be6d9d92ebecb8d7985708dd29c 100644 --- a/examples/canvas3d/canvas3d/textureandlight/main.cpp +++ b/examples/canvas3d/canvas3d/textureandlight/main.cpp @@ -69,6 +69,7 @@ int main(int argc, char *argv[]) viewer.setTitle(QStringLiteral("Textured and Lit Cube")); viewer.setResizeMode(QQuickView::SizeRootObjectToView); + viewer.setColor("#fafafa"); viewer.show(); return app.exec(); diff --git a/examples/canvas3d/canvas3d/textureandlight/qml/textureandlight/main.qml b/examples/canvas3d/canvas3d/textureandlight/qml/textureandlight/main.qml index bb951b440ff739ae1f59e4839ca099c9e649a56f..cd308bf14806e26c1478c4c29fdcdd1345176eeb 100644 --- a/examples/canvas3d/canvas3d/textureandlight/qml/textureandlight/main.qml +++ b/examples/canvas3d/canvas3d/textureandlight/qml/textureandlight/main.qml @@ -41,12 +41,11 @@ import QtCanvas3D 1.0 import "textureandlight.js" as GLCode //! [4] -Rectangle { +Item { id: mainview width: 1280 height: 768 visible: true - color: "#fafafa" //! [0] Canvas3D { diff --git a/examples/canvas3d/canvas3d/textureandlight/qml/textureandlight/textureandlight.js b/examples/canvas3d/canvas3d/textureandlight/qml/textureandlight/textureandlight.js index d80cb8fa54557870eac4c2d2e23927c712e74e30..4c1a5223323670a83e5a94af2069fdc8c7a0f876 100644 --- a/examples/canvas3d/canvas3d/textureandlight/qml/textureandlight/textureandlight.js +++ b/examples/canvas3d/canvas3d/textureandlight/qml/textureandlight/textureandlight.js @@ -63,7 +63,7 @@ function initializeGL(canvas) { canvas3d = canvas; //! [1] // Get the OpenGL context object that represents the API we call - gl = canvas.getContext("canvas3d", {depth:true, antialias:true}); + gl = canvas.getContext("canvas3d", {depth:true, antialias:true, alpha:false}); //! [1] //! [2] diff --git a/examples/canvas3d/canvas3d/threejs/cellphone/qml/cellphone/cellphone.js b/examples/canvas3d/canvas3d/threejs/cellphone/qml/cellphone/cellphone.js index 42c7e52deb3494192f6c564fc775efc2f4d2c1aa..160ae4e9d13e5c35b8ac9bf8355e02f56b427385 100644 --- a/examples/canvas3d/canvas3d/threejs/cellphone/qml/cellphone/cellphone.js +++ b/examples/canvas3d/canvas3d/threejs/cellphone/qml/cellphone/cellphone.js @@ -98,8 +98,7 @@ function initializeGL(canvas, textureSource) { } renderer = new THREE.Canvas3DRenderer( - { canvas: canvas, antialias: true, devicePixelRatio: canvas.devicePixelRatio, - alpha: true}); + { canvas: canvas, antialias: true, devicePixelRatio: canvas.devicePixelRatio }); renderer.setSize( canvas.width, canvas.height ); // The cellphone meshes were created using a third party tool (Blender). diff --git a/examples/canvas3d/canvas3d/threejs/cellphone/qml/cellphone/main.qml b/examples/canvas3d/canvas3d/threejs/cellphone/qml/cellphone/main.qml index 559847819731d8ce5ce82b1061cc32892269867d..ad27b16750362533dcd78a8cbadb99db91b0899e 100644 --- a/examples/canvas3d/canvas3d/threejs/cellphone/qml/cellphone/main.qml +++ b/examples/canvas3d/canvas3d/threejs/cellphone/qml/cellphone/main.qml @@ -45,6 +45,7 @@ Window { height: initialHeight visible: true title: "Interactive Mobile Phone Demo" + color: "black" //! [0] Item { diff --git a/examples/canvas3d/canvas3d/threejs/oneqt/InfoSheet.qml b/examples/canvas3d/canvas3d/threejs/oneqt/InfoSheet.qml index f35a9503b07087d67bb7bc7c2742be8975e79c58..2808f6a2b994f443130002739f6ce378440af999 100644 --- a/examples/canvas3d/canvas3d/threejs/oneqt/InfoSheet.qml +++ b/examples/canvas3d/canvas3d/threejs/oneqt/InfoSheet.qml @@ -95,7 +95,7 @@ Item { id: heading1 text: "" font.family: "Helvetica" - font.pixelSize: 3.0 * 16 + font.pointSize: 36 font.weight: Font.Light color: "black" @@ -105,7 +105,7 @@ Item { anchors.left: heading1.right text: "" font.family: "Helvetica" - font.pixelSize: 3.0 * 16 + font.pointSize: 36 font.weight: Font.Light color: "#5caa15" } @@ -123,10 +123,8 @@ Item { text: "" width: (infoSheet.width - infoSheet.anchors.leftMargin) * 0.3 font.family: "Helvetica" - font.pixelSize: 16 + font.pointSize: 16 font.weight: Font.Light - lineHeight: 1.625 * 16 - lineHeightMode: Text.FixedHeight } } } diff --git a/examples/canvas3d/canvas3d/threejs/oneqt/Navibutton.qml b/examples/canvas3d/canvas3d/threejs/oneqt/Navibutton.qml index ca9b02b98c946ef9c04b66024823226c661de1a7..80f3f0202d193ff384a5b0aa2fd9aed87f301160 100644 --- a/examples/canvas3d/canvas3d/threejs/oneqt/Navibutton.qml +++ b/examples/canvas3d/canvas3d/threejs/oneqt/Navibutton.qml @@ -41,8 +41,7 @@ Text { id: menubarItem text: "" font.family: "Helvetica" - font.pixelSize: 1.125 * 16 - //font.pointSize: 20 + font.pointSize: 20 font.weight: Font.Light color: "#404244" Layout.alignment: Qt.AlignHCenter diff --git a/examples/canvas3d/canvas3d/threejs/oneqt/main.cpp b/examples/canvas3d/canvas3d/threejs/oneqt/main.cpp index 112cc20bf6ef3e631d51e33b936a6be8b43289b2..b68136aa28ec75ecc78ba5e07b8df82aae0053fe 100644 --- a/examples/canvas3d/canvas3d/threejs/oneqt/main.cpp +++ b/examples/canvas3d/canvas3d/threejs/oneqt/main.cpp @@ -59,6 +59,7 @@ int main(int argc, char *argv[]) viewer.setTitle(QStringLiteral("One Qt")); viewer.setResizeMode(QQuickView::SizeRootObjectToView); + viewer.setColor(QColor("#FCFCFC")); viewer.show(); return app.exec(); diff --git a/examples/canvas3d/canvas3d/threejs/oneqt/oneqt.qml b/examples/canvas3d/canvas3d/threejs/oneqt/oneqt.qml index cc1a16c1ec1b7ee64a12f5c4c0791132640422b4..8534f4cd5b60c4d92144d8889b3375cc7681d50d 100644 --- a/examples/canvas3d/canvas3d/threejs/oneqt/oneqt.qml +++ b/examples/canvas3d/canvas3d/threejs/oneqt/oneqt.qml @@ -38,13 +38,12 @@ import QtQuick 2.0 import QtCanvas3D 1.0 import QtQuick.Layouts 1.1 -Rectangle { +Item { id: mainview width: 1280 height: 768 visible: true focus: true - color: "#FCFCFC" Keys.onPressed: { if (event.key === Qt.Key_1) imageCube.state = 'image1'; @@ -307,7 +306,7 @@ Rectangle { text: "Your productivity is at the core of what drives us. We made creating embedded<br>"+ "devices agile and painless without sacrificing maximum native performance. You get<br>"+ "to write your application using high level C++ libraries with no need to worry about<br>"+ - "nasty platform details. Using Qt Creator IDE and with a variety of UI approaches to<br"+ + "nasty platform details. Using Qt Creator IDE and with a variety of UI approaches to<br>"+ "choose from you can create the optimal UX for your end users." } @@ -322,7 +321,7 @@ Rectangle { headingText2: "is Our Specialty" text: "We make cross-platform application development easy. Target all the screens in your<br>"+ "end users’ lives. You only need to write and maintain one code base regardless of<br>"+ - "what kind of and how many target platforms you might have and we’re talking about<br"+ + "what kind of and how many target platforms you might have and we’re talking about<br>"+ "all major operating systems here. No need for separate implementations for<br>"+ "different user devices. Qt makes your time-to-market faster, technology strategy<br>"+ "simpler and future-proof, consequently reducing costs." diff --git a/examples/canvas3d/canvas3d/threejs/planets/main.cpp b/examples/canvas3d/canvas3d/threejs/planets/main.cpp index 59b10c6ee6c70fea156fef50544c67134e390cc6..61ca3fc83b83a34dcd46214bf9fc13279af19174 100644 --- a/examples/canvas3d/canvas3d/threejs/planets/main.cpp +++ b/examples/canvas3d/canvas3d/threejs/planets/main.cpp @@ -59,6 +59,7 @@ int main(int argc, char *argv[]) viewer.setTitle(QStringLiteral("Qt Canvas 3D Examples - Planets")); viewer.setResizeMode(QQuickView::SizeRootObjectToView); + viewer.setColor(Qt::black); viewer.show(); return app.exec(); diff --git a/examples/canvas3d/canvas3d/threejs/planets/planets.qml b/examples/canvas3d/canvas3d/threejs/planets/planets.qml index e5c157848697e579dab3fefe40d38fc0ffbc6dca..9699590fb24dd7a485efc65cc490933e958366e0 100644 --- a/examples/canvas3d/canvas3d/threejs/planets/planets.qml +++ b/examples/canvas3d/canvas3d/threejs/planets/planets.qml @@ -39,12 +39,11 @@ import QtCanvas3D 1.0 import "planets.js" as GLCode -Rectangle { +Item { id: mainview width: 1280 height: 768 visible: true - color: "black" property int focusedPlanet: 100 property int oldPlanet: 0 property real xLookAtOffset: 0 diff --git a/src/imports/qtcanvas3d/abstractobject3d.cpp b/src/imports/qtcanvas3d/abstractobject3d.cpp index 794e9a423d5caf8dfc8f38cd81fe670d2a83b1c8..6023e1bf37fc0bbce930ec9ac852084c8cb34bc0 100644 --- a/src/imports/qtcanvas3d/abstractobject3d.cpp +++ b/src/imports/qtcanvas3d/abstractobject3d.cpp @@ -40,10 +40,22 @@ QT_BEGIN_NAMESPACE QT_CANVAS3D_BEGIN_NAMESPACE +/*! + * \qmltype Canvas3DAbstractObject + * \since QtCanvas3D 1.0 + * \inqmlmodule QtCanvas3D + * \brief Base type for Canvas3D types representing OpenGL resources. + * + * An uncreatable QML type that is the base type for other Canvas3D types that represent + * OpenGL resources. + */ + CanvasAbstractObject::CanvasAbstractObject(CanvasGlCommandQueue *queue, QObject *parent) : QObject(parent), m_hasName(false), + m_invalidated(false), m_commandQueue(queue) + { m_name = QString("0x%1").arg((long long) this, 0, 16); } @@ -52,6 +64,10 @@ CanvasAbstractObject::~CanvasAbstractObject() { } +/*! + * \qmlproperty string Canvas3DAbstractObject::name + * Name of the object. + */ void CanvasAbstractObject::setName(const QString &name) { if (m_name == name) @@ -73,5 +89,41 @@ bool CanvasAbstractObject::hasSpecificName() const return m_hasName; } +/*! + * \qmlproperty bool Canvas3DAbstractObject::invalidated + * Indicates if this object has been invalidated. Invalidated objects cannot be valid parameters + * in Context3D methods and will result in \c{Context3D.INVALID_OPERATION} error if used. + * Objects are invalidated when context is lost and cannot be validated again. + * + * \sa {Canvas3D::contextLost}{Canvas3D.contextLost} + */ +bool CanvasAbstractObject::invalidated() const +{ + return m_invalidated; +} + +void CanvasAbstractObject::setInvalidated(bool invalidated) +{ + m_invalidated = invalidated; +} + +void CanvasAbstractObject::queueCommand(CanvasGlCommandQueue::GlCommandId id, GLint p1, GLint p2) +{ + if (!m_invalidated) + m_commandQueue->queueCommand(id, p1, p2); +} + +void CanvasAbstractObject::queueCommand(CanvasGlCommandQueue::GlCommandId id, QByteArray *data, + GLint p1, GLint p2) +{ + if (!m_invalidated) { + GlCommand &command = m_commandQueue->queueCommand(id, p1, p2); + command.data = data; + } else { + // We need to delete the data since we are not passing it to a command + delete data; + } +} + QT_CANVAS3D_END_NAMESPACE QT_END_NAMESPACE diff --git a/src/imports/qtcanvas3d/abstractobject3d_p.h b/src/imports/qtcanvas3d/abstractobject3d_p.h index 988d2c001822813c4edb2574078780406b35f675..a1060f6e5261d415cffabde7c037cc367e5aa313 100644 --- a/src/imports/qtcanvas3d/abstractobject3d_p.h +++ b/src/imports/qtcanvas3d/abstractobject3d_p.h @@ -59,6 +59,7 @@ class CanvasAbstractObject : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) + Q_PROPERTY(bool invalidated READ invalidated NOTIFY invalidatedChanged) public: explicit CanvasAbstractObject(CanvasGlCommandQueue *queue, QObject *parent); @@ -67,15 +68,23 @@ public: void setName(const QString &name); const QString &name() const; bool hasSpecificName() const; + bool invalidated() const; + void setInvalidated(bool invalidated); // Internal signals: void nameChanged(const QString &name); + void invalidatedChanged(bool invalidated); + +protected: + void queueCommand(CanvasGlCommandQueue::GlCommandId id, GLint p1, GLint p2 = 0); + void queueCommand(CanvasGlCommandQueue::GlCommandId id, QByteArray *data, + GLint p1, GLint p2 = 0); + CanvasGlCommandQueue *commandQueue() const { return m_commandQueue; } private: QString m_name; bool m_hasName; - -protected: + bool m_invalidated; // Not owned. Can be null pointer if the object type doesn't need OpenGL commands CanvasGlCommandQueue *m_commandQueue; }; diff --git a/src/imports/qtcanvas3d/buffer3d.cpp b/src/imports/qtcanvas3d/buffer3d.cpp index 46137baf7c6b5b3370b8df31054c06b7698dff69..9e2a9821766b1e048cbbafcac0a791aa73ecc68b 100644 --- a/src/imports/qtcanvas3d/buffer3d.cpp +++ b/src/imports/qtcanvas3d/buffer3d.cpp @@ -46,6 +46,7 @@ QT_CANVAS3D_BEGIN_NAMESPACE * \qmltype Canvas3DBuffer * \since QtCanvas3D 1.0 * \inqmlmodule QtCanvas3D + * \inherits Canvas3DAbstractObject * \brief Contains an OpenGL buffer. * * An uncreatable QML type that contains an OpenGL buffer. You can get it by calling the @@ -65,13 +66,11 @@ CanvasBuffer::CanvasBuffer(CanvasGlCommandQueue *queue, QObject *parent) : m_bufferId(queue->createResourceId()), m_bindTarget(CanvasBuffer::UNINITIALIZED) { - Q_ASSERT(m_commandQueue); - - m_commandQueue->queueCommand(CanvasGlCommandQueue::glGenBuffers, m_bufferId); + queueCommand(CanvasGlCommandQueue::glGenBuffers, m_bufferId); } CanvasBuffer::CanvasBuffer(const CanvasBuffer& other) : - CanvasAbstractObject(other.m_commandQueue, 0), // Copying a QObject, leave it parentless.. + CanvasAbstractObject(other.commandQueue(), 0), // Copying a QObject, leave it parentless.. m_bufferId(other.m_bufferId), m_bindTarget(other.m_bindTarget) { @@ -85,9 +84,8 @@ CanvasBuffer::~CanvasBuffer() void CanvasBuffer::del() { - if (m_bufferId) { - m_commandQueue->queueCommand(CanvasGlCommandQueue::glDeleteBuffers, m_bufferId); - } + if (m_bufferId) + queueCommand(CanvasGlCommandQueue::glDeleteBuffers, m_bufferId); m_bufferId = 0; } diff --git a/src/imports/qtcanvas3d/canvas3d.cpp b/src/imports/qtcanvas3d/canvas3d.cpp index 39acb80f18c17df34aab76cd1d20c087b93d43ca..f8294c396680afe4f68ee5b70a169c384b83e099 100644 --- a/src/imports/qtcanvas3d/canvas3d.cpp +++ b/src/imports/qtcanvas3d/canvas3d.cpp @@ -83,7 +83,6 @@ Canvas::Canvas(QQuickItem *parent): QQuickItem(parent), m_isNeedRenderQueued(false), m_rendererReady(false), - m_context3D(0), m_fboSize(0, 0), m_maxSize(0, 0), m_frameTimeMs(0), @@ -91,24 +90,25 @@ Canvas::Canvas(QQuickItem *parent): m_maxSamples(0), m_devicePixelRatio(1.0f), m_isOpenGLES2(false), + m_isCombinedDepthStencilSupported(false), m_isSoftwareRendered(false), m_isContextAttribsSet(false), m_alphaChanged(false), m_resizeGLQueued(false), - m_firstSync(true), + m_allowRenderTargetChange(true), + m_renderTargetSyncConnected(false), m_renderTarget(RenderTargetOffscreenBuffer), m_renderOnDemand(false), m_renderer(0), m_maxVertexAttribs(0), m_contextVersion(0), - m_fps(0) + m_fps(0), + m_contextState(ContextNone) { connect(this, &QQuickItem::windowChanged, this, &Canvas::handleWindowChanged); connect(this, &Canvas::needRender, this, &Canvas::queueNextRender, Qt::QueuedConnection); connect(this, &QQuickItem::widthChanged, this, &Canvas::queueResizeGL, Qt::DirectConnection); connect(this, &QQuickItem::heightChanged, this, &Canvas::queueResizeGL, Qt::DirectConnection); - connect(this, &QQuickItem::widthChanged, this, &Canvas::widthChanged, Qt::DirectConnection); - connect(this, &QQuickItem::heightChanged, this, &Canvas::heightChanged, Qt::DirectConnection); setAntialiasing(false); // Set contents to false in case we are in qml designer to make component look nice @@ -132,64 +132,34 @@ Canvas::Canvas(QQuickItem *parent): * Driven by the Qt Quick scenegraph loop. */ -Canvas::~Canvas() -{ - // Ensure that all JS objects have been destroyed before we destroy the command queue. - delete m_context3D; - - if (m_renderer) { - if (m_renderer->thread() == QThread::currentThread()) - delete m_renderer; - else - m_renderer->deleteLater(); - } -} - /*! - * Override QQuickItem's setWidth to be able to limit the maximum canvas size to maximum viewport - * dimensions. + * \qmlsignal void Canvas3D::contextLost() + * Emitted when OpenGL context is lost. This happens whenever the parent window of the Canvas3D + * is destroyed (or otherwise loses its context), or Canvas3D is moved to a different window. + * Removing Canvas3D from a window and adding it back to the same window doesn't cause context + * loss, as long as the window itself stays alive. + * + * When context is lost, all objects created by Context3D are invalidated. + * + * \sa contextRestored */ -void Canvas::setWidth(int width) -{ - int newWidth = width; - int maxWidth = m_maxSize.width(); - if (maxWidth && width > maxWidth) { - qCDebug(canvas3drendering).nospace() << "Canvas3D::" << __FUNCTION__ - << "():" - << "Maximum width exceeded. Limiting to " - << maxWidth; - newWidth = maxWidth; - } - QQuickItem::setWidth(qreal(newWidth)); -} - -int Canvas::width() -{ - return int(QQuickItem::width()); -} /*! - * Override QQuickItem's setHeight to be able to limit the maximum canvas size to maximum viewport - * dimensions. + * \qmlsignal void Canvas3D::contextRestored() + * Emitted when OpenGL context is restored after a loss of context occurred. The Context3D attached + * to the canvas needs to be reinitialized, so initializeGL is also emitted after this signal. + * + * \sa contextLost */ -void Canvas::setHeight(int height) -{ - int newHeight = height; - int maxHeight = m_maxSize.height(); - if (maxHeight && height > maxHeight) { - qCDebug(canvas3drendering).nospace() << "Canvas3D::" << __FUNCTION__ - << "():" - << "Maximum height exceeded. Limiting to " - << maxHeight; - newHeight = maxHeight; - } - - QQuickItem::setHeight(qreal(newHeight)); -} -int Canvas::height() +Canvas::~Canvas() { - return int(QQuickItem::height()); + // Ensure that all JS objects have been destroyed before we destroy the command queue. + if (!m_context3D.isNull()) + delete m_context3D.data(); + + if (m_renderer) + m_renderer->destroy(); } /*! @@ -265,7 +235,7 @@ int Canvas::height() */ void Canvas::setRenderTarget(RenderTarget target) { - if (m_firstSync) { + if (m_allowRenderTargetChange) { RenderTarget oldTarget = m_renderTarget; m_renderTarget = target; if (m_renderTarget == RenderTargetOffscreenBuffer) @@ -274,6 +244,13 @@ void Canvas::setRenderTarget(RenderTarget target) setFlag(ItemHasContents, false); if (oldTarget != m_renderTarget) emit renderTargetChanged(); + if (!m_renderTargetSyncConnected && window() + && m_renderTarget != RenderTargetOffscreenBuffer) { + m_renderTargetSyncConnected = true; + connect(window(), &QQuickWindow::beforeSynchronizing, + this, &Canvas::handleBeforeSynchronizing, Qt::DirectConnection); + window()->setClearBeforeRendering(false); + } } else { qCWarning(canvas3drendering).nospace() << "Canvas3D::" << __FUNCTION__ << ": renderTarget property can only be " @@ -394,7 +371,8 @@ QJSValue Canvas::getContext(const QString &type, const QVariantMap &options) updateWindowParameters(); if (!m_renderer->createContext(window(), m_contextAttribs, m_maxVertexAttribs, m_maxSize, - m_contextVersion, m_extensions)) { + m_contextVersion, m_extensions, + m_isCombinedDepthStencilSupported)) { return QJSValue(QJSValue::NullValue); } @@ -405,37 +383,21 @@ QJSValue Canvas::getContext(const QString &type, const QVariantMap &options) m_context3D = new CanvasContext(QQmlEngine::contextForObject(this)->engine(), m_isOpenGLES2, m_maxVertexAttribs, m_contextVersion, m_extensions, - m_renderer->commandQueue()); + m_renderer->commandQueue(), + m_isCombinedDepthStencilSupported); connect(m_renderer, &CanvasRenderer::textureIdResolved, - m_context3D, &CanvasContext::handleTextureIdResolved, + m_context3D.data(), &CanvasContext::handleTextureIdResolved, Qt::QueuedConnection); - // Verify that width and height are not initially too large, in case width and height - // were set before getting GL_MAX_VIEWPORT_DIMS - if (width() > m_maxSize.width()) { - qCDebug(canvas3drendering).nospace() << "Canvas3D::" << __FUNCTION__ - << "():" - << "Maximum width exceeded. Limiting to " - << m_maxSize.width(); - QQuickItem::setWidth(m_maxSize.width()); - } - if (height() > m_maxSize.height()) { - qCDebug(canvas3drendering).nospace() << "Canvas3D::" << __FUNCTION__ - << "():" - << "Maximum height exceeded. Limiting to " - << m_maxSize.height(); - QQuickItem::setHeight(m_maxSize.height()); - } - m_context3D->setCanvas(this); m_context3D->setDevicePixelRatio(m_devicePixelRatio); m_context3D->setContextAttributes(m_contextAttribs); - emit contextChanged(m_context3D); + emit contextChanged(m_context3D.data()); } - return QQmlEngine::contextForObject(this)->engine()->newQObject(m_context3D); + return QQmlEngine::contextForObject(this)->engine()->newQObject(m_context3D.data()); } /*! @@ -488,10 +450,52 @@ void Canvas::setPixelSize(QSize pixelSize) void Canvas::handleWindowChanged(QQuickWindow *window) { qCDebug(canvas3drendering).nospace() << "Canvas3D::" << __FUNCTION__ << "(" << window << ")"; - if (!window) + + if (!window) { + if (!m_contextWindow.isNull()) { + if (m_renderTarget != RenderTargetOffscreenBuffer) { + disconnect(m_contextWindow.data(), &QQuickWindow::beforeSynchronizing, + this, &Canvas::handleBeforeSynchronizing); + } + if (m_renderer) { + if (m_renderTarget == RenderTargetForeground) { + disconnect(m_contextWindow.data(), &QQuickWindow::beforeRendering, + m_renderer, &CanvasRenderer::clearBackground); + disconnect(m_contextWindow.data(), &QQuickWindow::afterRendering, + m_renderer, &CanvasRenderer::render); + } else { + disconnect(m_contextWindow.data(), &QQuickWindow::beforeRendering, + m_renderer, &CanvasRenderer::render); + } + } + } return; + } - if (m_renderTarget != RenderTargetOffscreenBuffer) { + if (window != m_contextWindow.data()) { + handleContextLost(); + m_contextWindow = window; + } else { + // Re-added to same window + if (!m_context3D.isNull()) + m_context3D->markQuickTexturesDirty(); + + if (m_renderer) { + if (m_renderTarget == RenderTargetForeground) { + connect(window, &QQuickWindow::beforeRendering, + m_renderer, &CanvasRenderer::clearBackground, Qt::DirectConnection); + connect(window, &QQuickWindow::afterRendering, + m_renderer, &CanvasRenderer::render, Qt::DirectConnection); + } else { + connect(window, &QQuickWindow::beforeRendering, + m_renderer, &CanvasRenderer::render, Qt::DirectConnection); + } + } + } + + if ((!m_allowRenderTargetChange || !m_renderTargetSyncConnected) + && m_renderTarget != RenderTargetOffscreenBuffer) { + m_renderTargetSyncConnected = true; connect(window, &QQuickWindow::beforeSynchronizing, this, &Canvas::handleBeforeSynchronizing, Qt::DirectConnection); window->setClearBeforeRendering(false); @@ -530,7 +534,7 @@ void Canvas::itemChange(ItemChange change, const ItemChangeData &value) CanvasContext *Canvas::context() { qCDebug(canvas3drendering).nospace() << "Canvas3D::" << __FUNCTION__ << "()"; - return m_context3D; + return m_context3D.data(); } void Canvas::updateWindowParameters() @@ -550,7 +554,7 @@ void Canvas::updateWindowParameters() } } - if (m_context3D) { + if (!m_context3D.isNull()) { if (m_context3D->devicePixelRatio() != m_devicePixelRatio) m_context3D->setDevicePixelRatio(m_devicePixelRatio); } @@ -576,15 +580,26 @@ bool Canvas::firstSync() { qCDebug(canvas3drendering).nospace() << "Canvas3D::" << __FUNCTION__ << "()"; - if (!m_renderer) { - m_renderer = new CanvasRenderer(); + if (m_contextState == ContextLost || !m_renderer) { + if (m_renderer) + m_renderer->destroy(); + m_renderer = new CanvasRenderer(); + m_contextState = ContextRestoring; + + // Update necessary things to m_context3D + if (!m_context3D.isNull()) { + m_context3D->setCommandQueue(m_renderer->commandQueue()); + connect(m_renderer, &CanvasRenderer::textureIdResolved, + m_context3D.data(), &CanvasContext::handleTextureIdResolved, + Qt::QueuedConnection); + } connect(m_renderer, &CanvasRenderer::fpsChanged, this, &Canvas::handleRendererFpsChange); } if (!m_renderer->qtContextResolved()) { - m_firstSync = false; + m_allowRenderTargetChange = false; QSize initializedSize = boundingRect().size().toSize(); if (initializedSize.width() <= 0) initializedSize.setWidth(1); @@ -597,7 +612,7 @@ bool Canvas::firstSync() m_renderer->getQtContextAttributes(m_contextAttribs); m_isContextAttribsSet = true; m_renderer->init(window(), m_contextAttribs, m_maxVertexAttribs, m_maxSize, - m_contextVersion, m_extensions); + m_contextVersion, m_extensions, m_isCombinedDepthStencilSupported); setPixelSize(m_renderer->fboSize()); } else { m_renderer->createContextShare(); @@ -606,6 +621,9 @@ bool Canvas::firstSync() connect(window(), &QQuickWindow::sceneGraphInvalidated, m_renderer, &CanvasRenderer::shutDown, Qt::DirectConnection); + connect(window(), &QQuickWindow::sceneGraphInvalidated, + this, &Canvas::handleContextLost, Qt::QueuedConnection); + connect(window(), &QObject::destroyed, this, &Canvas::handleContextLost); if (m_renderTarget == RenderTargetForeground) { connect(window(), &QQuickWindow::beforeRendering, @@ -770,16 +788,23 @@ void Canvas::queueNextRender() return; } - if (!m_context3D) { + if (m_context3D.isNull() || m_contextState == ContextRestoring) { // Call the initialize function from QML/JavaScript. It'll call the getContext() // that in turn creates the renderer context. qCDebug(canvas3drendering).nospace() << "Canvas3D::" << __FUNCTION__ << " Emit initializeGL() signal"; + if (!m_context3D.isNull()) { + m_context3D->setContextLostState(false); + emit contextRestored(); + } + // Call init on JavaScript side to queue the user's GL initialization commands. // The initial context creation get will also initialize the context command queue. emit initializeGL(); + m_contextState = ContextAlive; + if (!m_isContextAttribsSet) { qCDebug(canvas3drendering).nospace() << "Canvas3D::" << __FUNCTION__ << " Context attributes not set, returning"; @@ -882,5 +907,26 @@ void Canvas::handleRendererFpsChange(uint fps) } } +void Canvas::handleContextLost() +{ + if (m_contextState == ContextAlive || m_contextState == ContextRestoring) { + m_contextState = ContextLost; + m_rendererReady = false; + m_fboSize = QSize(0, 0); + + if (!m_contextWindow.isNull()) { + disconnect(m_contextWindow.data(), &QQuickWindow::sceneGraphInvalidated, + this, &Canvas::handleContextLost); + disconnect(m_contextWindow.data(), &QObject::destroyed, + this, &Canvas::handleContextLost); + } + + if (!m_context3D.isNull()) + m_context3D->setContextLostState(true); + + emit contextLost(); + } +} + QT_CANVAS3D_END_NAMESPACE QT_END_NAMESPACE diff --git a/src/imports/qtcanvas3d/canvas3d_p.h b/src/imports/qtcanvas3d/canvas3d_p.h index 9ce63e2fc5e46374ce44eb40d5cea3a89bc71f8d..1cf67ab3e32c7392a234f324aa9084b1dfbcdb17 100644 --- a/src/imports/qtcanvas3d/canvas3d_p.h +++ b/src/imports/qtcanvas3d/canvas3d_p.h @@ -54,6 +54,7 @@ #include <QtQuick/QQuickWindow> #include <QtGui/QOpenGLFramebufferObject> #include <QtCore/QElapsedTimer> +#include <QtCore/QPointer> QT_BEGIN_NAMESPACE @@ -88,8 +89,6 @@ class QT_CANVAS3D_EXPORT Canvas : public QQuickItem Q_PROPERTY(float devicePixelRatio READ devicePixelRatio NOTIFY devicePixelRatioChanged) Q_PROPERTY(uint fps READ fps NOTIFY fpsChanged) Q_PROPERTY(QSize pixelSize READ pixelSize WRITE setPixelSize NOTIFY pixelSizeChanged) - Q_PROPERTY(int width READ width WRITE setWidth NOTIFY widthChanged) - Q_PROPERTY(int height READ height WRITE setHeight NOTIFY heightChanged) Q_PROPERTY(RenderTarget renderTarget READ renderTarget WRITE setRenderTarget NOTIFY renderTargetChanged REVISION 1) Q_PROPERTY(bool renderOnDemand READ renderOnDemand WRITE setRenderOnDemand NOTIFY renderOnDemandChanged REVISION 1) @@ -100,6 +99,14 @@ public: RenderTargetForeground }; + // internal + enum ContextState { + ContextNone, + ContextLost, + ContextRestoring, + ContextAlive + }; + Canvas(QQuickItem *parent = 0); ~Canvas(); @@ -107,10 +114,6 @@ public: float devicePixelRatio(); QSize pixelSize(); void setPixelSize(QSize pixelSize); - void setWidth(int width); - int width(); - void setHeight(int height); - int height(); void setRenderTarget(RenderTarget target); RenderTarget renderTarget() const; void setRenderOnDemand(bool enable); @@ -126,12 +129,15 @@ public: CanvasRenderer *renderer(); public slots: + void requestRender(); + +private slots: void queueNextRender(); void queueResizeGL(); - void requestRender(); void emitNeedRender(); void handleBeforeSynchronizing(); void handleRendererFpsChange(uint fps); + void handleContextLost(); signals: void needRender(); @@ -139,10 +145,10 @@ signals: void contextChanged(CanvasContext *context); void fpsChanged(uint fps); void pixelSizeChanged(QSize pixelSize); - void widthChanged(); - void heightChanged(); void renderTargetChanged(); void renderOnDemandChanged(); + void contextLost(); + void contextRestored(); void initializeGL(); void paintGL(); @@ -163,7 +169,7 @@ private: bool m_isNeedRenderQueued; bool m_rendererReady; - CanvasContext *m_context3D; + QPointer<CanvasContext> m_context3D; QSize m_fboSize; QSize m_maxSize; @@ -174,13 +180,15 @@ private: float m_devicePixelRatio; bool m_isOpenGLES2; + bool m_isCombinedDepthStencilSupported; bool m_isSoftwareRendered; bool m_runningInDesigner; CanvasContextAttributes m_contextAttribs; bool m_isContextAttribsSet; bool m_alphaChanged; bool m_resizeGLQueued; - bool m_firstSync; + bool m_allowRenderTargetChange; + bool m_renderTargetSyncConnected; RenderTarget m_renderTarget; bool m_renderOnDemand; @@ -191,6 +199,9 @@ private: QSet<QByteArray> m_extensions; uint m_fps; + + ContextState m_contextState; + QPointer<QQuickWindow> m_contextWindow; // Not owned }; QT_CANVAS3D_END_NAMESPACE diff --git a/src/imports/qtcanvas3d/canvasglstatedump.cpp b/src/imports/qtcanvas3d/canvasglstatedump.cpp index 7b46e4ecf1836cedfbac2f97a9c441d6f1432edf..35698484db7bd50e63b7ed94a4b5fbaa2d4bd3e3 100644 --- a/src/imports/qtcanvas3d/canvasglstatedump.cpp +++ b/src/imports/qtcanvas3d/canvasglstatedump.cpp @@ -433,6 +433,9 @@ void CanvasGLStateDump::doGLStateDump() */ QString CanvasGLStateDump::getGLStateDump(CanvasGLStateDump::stateDumpEnums options) { + if (m_canvasContext->isContextLost()) + return QString(); + m_options = options; m_stateDumpStr.clear(); diff --git a/src/imports/qtcanvas3d/canvasrenderer.cpp b/src/imports/qtcanvas3d/canvasrenderer.cpp index 61778be55d1a9cbefc3c47113094d00f1ad39e77..86ca838e26b4106488985ba2504bd3d4fb406e0d 100644 --- a/src/imports/qtcanvas3d/canvasrenderer.cpp +++ b/src/imports/qtcanvas3d/canvasrenderer.cpp @@ -46,6 +46,7 @@ #include <QtQml/QQmlEngine> #include <QtQml/QQmlContext> #include <QtQuick/QQuickWindow> +#include <QtCore/QMutexLocker> QT_BEGIN_NAMESPACE QT_CANVAS3D_BEGIN_NAMESPACE @@ -136,7 +137,7 @@ void CanvasRenderer::createContextShare() m_glContextQt->doneCurrent(); if (!m_glContextShare->create()) { qCWarning(canvas3drendering).nospace() << "CanvasRenderer::" << __FUNCTION__ - << "Failed to create share context"; + << " Failed to create share context"; } if (!m_glContextQt->makeCurrent(surface)) { qCWarning(canvas3drendering).nospace() << "CanvasRenderer::" << __FUNCTION__ @@ -157,7 +158,7 @@ void CanvasRenderer::getQtContextAttributes(CanvasContextAttributes &contextAttr void CanvasRenderer::init(QQuickWindow *window, const CanvasContextAttributes &contextAttributes, GLint &maxVertexAttribs, QSize &maxSize, int &contextVersion, - QSet<QByteArray> &extensions) + QSet<QByteArray> &extensions, bool &isCombinedDepthStencilSupported) { m_antialias = contextAttributes.antialias(); m_preserveDrawingBuffer = contextAttributes.preserveDrawingBuffer(); @@ -177,6 +178,10 @@ void CanvasRenderer::init(QQuickWindow *window, const CanvasContextAttributes &c maxSize.setHeight(viewportDims[1]); // Set the size + if (maxSize.width() < m_initializedSize.width()) + m_initializedSize.setWidth(maxSize.width()); + if (maxSize.height() < m_initializedSize.height()) + m_initializedSize.setHeight(maxSize.height()); setFboSize(m_initializedSize); m_forceViewportRect = QRect(0, 0, m_fboSize.width(), m_fboSize.height()); glScissor(0, 0, m_fboSize.width(), m_fboSize.height()); @@ -223,6 +228,19 @@ void CanvasRenderer::init(QQuickWindow *window, const CanvasContextAttributes &c contextVersion = m_glContext->format().majorVersion(); + if (contextVersion < 3) { + if (m_isOpenGLES2) { + isCombinedDepthStencilSupported = + m_glContext->hasExtension(QByteArrayLiteral("GL_OES_packed_depth_stencil")); + } else { + isCombinedDepthStencilSupported = + m_glContext->hasExtension(QByteArrayLiteral("GL_ARB_framebuffer_object")) + || m_glContext->hasExtension(QByteArrayLiteral("GL_EXT_packed_depth_stencil")); + } + } else { + isCombinedDepthStencilSupported = true; + } + extensions = m_glContext->extensions(); if (!m_alphaMultiplierProgram) { @@ -300,7 +318,7 @@ void CanvasRenderer::shutDown() if (!m_glContext) return; - disconnect(m_contextWindow, 0, this, 0); + QMutexLocker locker(&m_shutdownMutex); m_fps = 0; @@ -364,7 +382,8 @@ void CanvasRenderer::shutDown() bool CanvasRenderer::createContext(QQuickWindow *window, const CanvasContextAttributes &contextAttributes, GLint &maxVertexAttribs, QSize &maxSize, - int &contextVersion, QSet<QByteArray> &extensions) + int &contextVersion, QSet<QByteArray> &extensions, + bool &isCombinedDepthStencilSupported) { // Initialize the swap buffer chain if (contextAttributes.depth() && contextAttributes.stencil() && !contextAttributes.antialias()) @@ -453,7 +472,8 @@ bool CanvasRenderer::createContext(QQuickWindow *window, return false; } - init(window, contextAttributes, maxVertexAttribs, maxSize, contextVersion, extensions); + init(window, contextAttributes, maxVertexAttribs, maxSize, contextVersion, extensions, + isCombinedDepthStencilSupported); if (m_glContext->thread() != contextThread) { m_glContext->doneCurrent(); @@ -1949,5 +1969,19 @@ qint64 CanvasRenderer::previousFrameTime() return m_frameTimeMs; } +void CanvasRenderer::destroy() +{ + QMutexLocker locker(&m_shutdownMutex); + + if (m_glContext) { + deleteLater(); + } else { + // It is safe to delete even in another thread if we are already shut down or not yet + // started up. + locker.unlock(); + delete this; + } +} + QT_CANVAS3D_END_NAMESPACE QT_END_NAMESPACE diff --git a/src/imports/qtcanvas3d/canvasrenderer_p.h b/src/imports/qtcanvas3d/canvasrenderer_p.h index 0740e9bb99c7da49bc51f4d32ad604a5628e0504..2f98974aa4dabdbf26548b31092635eb53381f89 100644 --- a/src/imports/qtcanvas3d/canvasrenderer_p.h +++ b/src/imports/qtcanvas3d/canvasrenderer_p.h @@ -58,6 +58,7 @@ #include <QtGui/QOpenGLFunctions> #include <QtGui/QOpenGLFramebufferObject> #include <QtQuick/QQuickItem> +#include <QtCore/QMutex> QT_BEGIN_NAMESPACE @@ -83,10 +84,10 @@ public: void getQtContextAttributes(CanvasContextAttributes &contextAttributes); void init(QQuickWindow *window, const CanvasContextAttributes &contextAttributes, GLint &maxVertexAttribs, QSize &maxSize, int &contextVersion, - QSet<QByteArray> &extensions); + QSet<QByteArray> &extensions, bool &isCombinedDepthStencilSupported); bool createContext(QQuickWindow *window, const CanvasContextAttributes &contextAttributes, GLint &maxVertexAttribs, QSize &maxSize, int &contextVersion, - QSet<QByteArray> &extensions); + QSet<QByteArray> &extensions, bool &isCombinedDepthStencilSupported); void createFBOs(); void bindCurrentRenderTarget(); @@ -114,6 +115,7 @@ public: void deleteCommandData(); qint64 previousFrameTime(); + void destroy(); public slots: void shutDown(); @@ -183,6 +185,7 @@ private: bool m_textureFinalized; GLbitfield m_clearMask; + QMutex m_shutdownMutex; }; QT_CANVAS3D_END_NAMESPACE diff --git a/src/imports/qtcanvas3d/context3d.cpp b/src/imports/qtcanvas3d/context3d.cpp index 829d5a90bed6e293f8b629435327946c8d6e036b..8700319fea2fbb7c36d15d4ef3210b0d631e0a0d 100644 --- a/src/imports/qtcanvas3d/context3d.cpp +++ b/src/imports/qtcanvas3d/context3d.cpp @@ -66,6 +66,10 @@ QT_CANVAS3D_BEGIN_NAMESPACE const int maxUniformAttributeNameLen = 512; +#ifndef GL_DEPTH24_STENCIL8 +#define GL_DEPTH24_STENCIL8 0x88F0 +#endif + /*! * \qmltype Context3D * \since QtCanvas3D 1.0 @@ -78,10 +82,10 @@ const int maxUniformAttributeNameLen = 512; * * \sa Canvas3D */ - CanvasContext::CanvasContext(QQmlEngine *engine, bool isES2, int maxVertexAttribs, int contextVersion, const QSet<QByteArray> &extensions, - CanvasGlCommandQueue *commandQueue, QObject *parent) : + CanvasGlCommandQueue *commandQueue, + bool isCombinedDepthStencilSupported, QObject *parent) : CanvasAbstractObject(0, parent), m_engine(engine), m_v4engine(QQmlEnginePrivate::getV4Engine(engine)), @@ -102,16 +106,17 @@ CanvasContext::CanvasContext(QQmlEngine *engine, bool isES2, int maxVertexAttrib m_maxVertexAttribs(maxVertexAttribs), m_contextVersion(contextVersion), m_isOpenGLES2(isES2), - m_commandQueue(commandQueue), + m_isCombinedDepthStencilSupported(isCombinedDepthStencilSupported), + m_commandQueue(0), + m_contextLost(false), + m_contextLostErrorReported(false), m_stateDumpExt(0), m_textureProviderExt(0), m_standardDerivatives(0), m_compressedTextureS3TC(0), m_compressedTexturePVRTC(0) { - connect(m_commandQueue, &CanvasGlCommandQueue::queueFull, - this, &CanvasContext::handleFullCommandQueue, Qt::DirectConnection); - + setCommandQueue(commandQueue); } CanvasContext::~CanvasContext() @@ -257,13 +262,15 @@ QJSValue CanvasContext::getShaderPrecisionFormat(glEnums shadertype, break; } - // On desktop envs glGetShaderPrecisionFormat is part of OpenGL 4.x, so it is not necessarily - // available. Let's just return the default values if not ES2. - if (m_isOpenGLES2) { - GlSyncCommand syncCommand(CanvasGlCommandQueue::glGetShaderPrecisionFormat, - GLint(shadertype), GLint(precisiontype)); - syncCommand.returnValue = retval; - scheduleSyncCommand(&syncCommand); + if (!checkContextLost()) { + // On desktop envs glGetShaderPrecisionFormat is part of OpenGL 4.x, so it is not necessarily + // available. Let's just return the default values if not ES2. + if (m_isOpenGLES2) { + GlSyncCommand syncCommand(CanvasGlCommandQueue::glGetShaderPrecisionFormat, + GLint(shadertype), GLint(precisiontype)); + syncCommand.returnValue = retval; + scheduleSyncCommand(&syncCommand); + } } CanvasShaderPrecisionFormat *format = new CanvasShaderPrecisionFormat(); @@ -275,13 +282,15 @@ QJSValue CanvasContext::getShaderPrecisionFormat(glEnums shadertype, /*! * \qmlmethod bool Context3D::isContextLost() - * Always returns false. + * Returns \c{true} if the context is currently lost. + * + * \sa {Canvas3D::contextLost}{Canvas3D.contextLost} */ bool CanvasContext::isContextLost() { qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ - << "(): false"; - return false; + << "(): " << m_contextLost; + return m_contextLost; } /*! @@ -292,19 +301,23 @@ QJSValue CanvasContext::getContextAttributes() { qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "()"; - CanvasContextAttributes *attributes = new CanvasContextAttributes(); - attributes->setAlpha(m_contextAttributes.alpha()); - attributes->setDepth(m_contextAttributes.depth()); - attributes->setStencil(m_contextAttributes.stencil()); - attributes->setAntialias(m_contextAttributes.antialias()); - attributes->setPremultipliedAlpha(m_contextAttributes.premultipliedAlpha()); - attributes->setPreserveDrawingBuffer(m_contextAttributes.preserveDrawingBuffer()); - attributes->setPreferLowPowerToHighPerformance( - m_contextAttributes.preferLowPowerToHighPerformance()); - attributes->setFailIfMajorPerformanceCaveat( - m_contextAttributes.failIfMajorPerformanceCaveat()); + if (checkContextLost()) { + return QJSValue(QJSValue::NullValue); + } else { + CanvasContextAttributes *attributes = new CanvasContextAttributes(); + attributes->setAlpha(m_contextAttributes.alpha()); + attributes->setDepth(m_contextAttributes.depth()); + attributes->setStencil(m_contextAttributes.stencil()); + attributes->setAntialias(m_contextAttributes.antialias()); + attributes->setPremultipliedAlpha(m_contextAttributes.premultipliedAlpha()); + attributes->setPreserveDrawingBuffer(m_contextAttributes.preserveDrawingBuffer()); + attributes->setPreferLowPowerToHighPerformance( + m_contextAttributes.preferLowPowerToHighPerformance()); + attributes->setFailIfMajorPerformanceCaveat( + m_contextAttributes.failIfMajorPerformanceCaveat()); - return m_engine->newQObject(attributes); + return m_engine->newQObject(attributes); + } } /*! @@ -313,8 +326,10 @@ QJSValue CanvasContext::getContextAttributes() */ void CanvasContext::flush() { - qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ - << "()"; + qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "()"; + + if (checkContextLost()) + return; m_commandQueue->queueCommand(CanvasGlCommandQueue::glFlush); } @@ -326,8 +341,11 @@ void CanvasContext::flush() */ void CanvasContext::finish() { - qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ - << "()"; + qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "()"; + + if (checkContextLost()) + return; + GlSyncCommand syncCommand(CanvasGlCommandQueue::glFinish); scheduleSyncCommand(&syncCommand); } @@ -338,10 +356,14 @@ void CanvasContext::finish() */ QJSValue CanvasContext::createTexture() { + if (checkContextLost()) + return QJSValue(QJSValue::NullValue); + CanvasTexture *texture = new CanvasTexture(m_commandQueue, this); QJSValue value = m_engine->newQObject(texture); qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "():" << value.toString(); + addObjectToValidList(texture); return value; } @@ -359,7 +381,7 @@ void CanvasContext::deleteTexture(QJSValue texture3D) CanvasTexture *texture = getAsTexture3D(texture3D); if (texture) { - if (!checkParent(texture, __FUNCTION__)) + if (!checkValidity(texture, __FUNCTION__)) return; texture->del(); } else { @@ -387,6 +409,9 @@ void CanvasContext::scissor(int x, int y, int width, int height) << ", height:" << height << ")"; + if (checkContextLost()) + return; + m_commandQueue->queueCommand(CanvasGlCommandQueue::glScissor, GLint(x), GLint(y), GLint(width), GLint(height)); } @@ -403,6 +428,9 @@ void CanvasContext::activeTexture(glEnums texture) qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "(texture:" << glEnumToString(texture) << ")"; + if (checkContextLost()) + return; + m_commandQueue->queueCommand(CanvasGlCommandQueue::glActiveTexture, GLint(texture)); } @@ -433,7 +461,7 @@ void CanvasContext::bindTexture(glEnums target, QJSValue texture3D) return; } - if (texture && checkParent(texture, __FUNCTION__)) { + if (texture && checkValidity(texture, __FUNCTION__)) { if (target == TEXTURE_2D) m_currentTexture2D->bind(target); else if (target == TEXTURE_CUBE_MAP) @@ -445,6 +473,9 @@ void CanvasContext::bindTexture(glEnums target, QJSValue texture3D) bool CanvasContext::isValidTextureBound(glEnums target, const QString &funcName, bool singleLayer) { + if (checkContextLost()) + return false; + switch (target) { case TEXTURE_2D: if (!m_currentTexture2D) { @@ -503,16 +534,30 @@ bool CanvasContext::isValidTextureBound(glEnums target, const QString &funcName, return true; } -bool CanvasContext::checkParent(QObject *obj, const char *function) +bool CanvasContext::checkValidity(CanvasAbstractObject *obj, const char *function) { - if (obj && obj->parent() != this) { - m_error |= CANVAS_INVALID_OPERATION; - qCWarning(canvas3drendering).nospace() << "Context3D::" << function - << ":INVALID_OPERATION:" - << "Object from wrong context"; - return false; + if (obj) { + if (obj->invalidated()) { + m_error |= CANVAS_INVALID_OPERATION; + qCWarning(canvas3drendering).nospace() << "Context3D::" << function + << ":INVALID_OPERATION:" + << "Object is invalid"; + return false; + } else if (obj->parent() != this) { + m_error |= CANVAS_INVALID_OPERATION; + qCWarning(canvas3drendering).nospace() << "Context3D::" << function + << ":INVALID_OPERATION:" + << "Object from wrong context"; + return false; + } + return true; } - return true; + + m_error |= CANVAS_INVALID_OPERATION; + qCWarning(canvas3drendering).nospace() << "Context3D::" << function + << ":INVALID_OPERATION:" + << "Null object"; + return false; } /*! @@ -561,7 +606,7 @@ void CanvasContext::uniformMatrixNfv(int dim, const QJSValue &location3D, bool t CanvasUniformLocation *locationObj = static_cast<CanvasUniformLocation *>(location3D.toQObject()); - if (!checkParent(locationObj, __FUNCTION__)) + if (!checkValidity(locationObj, __FUNCTION__)) return; // Check if we have a JavaScript array @@ -695,7 +740,7 @@ bool CanvasContext::isTexture(QJSValue anyObject) << ")"; CanvasTexture *texture = getAsTexture3D(anyObject); - if (texture && checkParent(texture, __FUNCTION__)) { + if (texture && checkValidity(texture, __FUNCTION__)) { GLboolean boolValue; GlSyncCommand syncCommand(CanvasGlCommandQueue::glIsTexture, texture->textureId()); syncCommand.returnValue = &boolValue; @@ -949,7 +994,7 @@ bool CanvasContext::checkTextureFormats(glEnums internalFormat, glEnums format) if (format != internalFormat) { qCWarning(canvas3drendering).nospace() << "Context3D::texImage2D()" << ":INVALID_OPERATION:" - << "internalFormat doesn't match format"; + << "internalFormat doesn't match format"; m_error |= CANVAS_INVALID_OPERATION; return false; } @@ -957,6 +1002,17 @@ bool CanvasContext::checkTextureFormats(glEnums internalFormat, glEnums format) return true; } +bool CanvasContext::checkContextLost() +{ + if (m_contextLost) { + qCWarning(canvas3drendering).nospace() << "Context3D::checkContextValid()" + << ":CONTEXT LOST:" + << "Context has been lost"; + return true; + } + return false; +} + /*! * \qmlmethod void Context3D::texImage2D(glEnums target, int level, glEnums internalformat, int width, int height, int border, glEnums format, glEnums type, TypedArray pixels) * Specify a 2D texture image. @@ -1659,11 +1715,14 @@ int CanvasContext::getSufficientSize(glEnums internalFormat, int width, int heig */ QJSValue CanvasContext::createFramebuffer() { + if (checkContextLost()) + return QJSValue(QJSValue::NullValue); + CanvasFrameBuffer *framebuffer = new CanvasFrameBuffer(m_commandQueue, this); QJSValue value = m_engine->newQObject(framebuffer); qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << ":" << value.toString(); - + addObjectToValidList(framebuffer); return value; } @@ -1689,14 +1748,15 @@ void CanvasContext::bindFramebuffer(glEnums target, QJSValue buffer) CanvasFrameBuffer *framebuffer = getAsFramebuffer(buffer); GLint bindId = 0; - if (framebuffer && checkParent(framebuffer, __FUNCTION__)) { + if (framebuffer && checkValidity(framebuffer, __FUNCTION__)) { m_currentFramebuffer = framebuffer; bindId = framebuffer->id(); } else { m_currentFramebuffer = 0; } - m_commandQueue->queueCommand(CanvasGlCommandQueue::glBindFramebuffer, bindId); + if (!checkContextLost()) + m_commandQueue->queueCommand(CanvasGlCommandQueue::glBindFramebuffer, bindId); } /*! @@ -1710,6 +1770,9 @@ CanvasContext::glEnums CanvasContext::checkFramebufferStatus(glEnums target) qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "(target:" << glEnumToString(target) << ")"; + if (checkContextLost()) + return FRAMEBUFFER_UNSUPPORTED; + if (target != FRAMEBUFFER) { qCWarning(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << ": INVALID_ENUM bind target, must be FRAMEBUFFER"; @@ -1771,22 +1834,38 @@ void CanvasContext::framebufferRenderbuffer(glEnums target, glEnums attachment, } CanvasRenderBuffer *renderbuffer = getAsRenderbuffer3D(renderbuffer3D); - if (renderbuffer && renderbuffertarget != RENDERBUFFER) { - qCWarning(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ - << "(): INVALID_OPERATION renderbuffertarget must be" - << " RENDERBUFFER for non null renderbuffers"; - m_error |= CANVAS_INVALID_OPERATION; - return; + if (renderbuffer) { + if (renderbuffertarget != RENDERBUFFER) { + qCWarning(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ + << "(): INVALID_OPERATION renderbuffertarget must be" + << " RENDERBUFFER for non null renderbuffers"; + m_error |= CANVAS_INVALID_OPERATION; + return; + } + if (!checkValidity(renderbuffer, __FUNCTION__)) + return; } - if (!checkParent(renderbuffer, __FUNCTION__)) - return; GLint renderbufferId = renderbuffer ? renderbuffer->id() : 0; - m_commandQueue->queueCommand(CanvasGlCommandQueue::glFramebufferRenderbuffer, - GLint(target), GLint(attachment), - GLint(renderbuffertarget), - GLint(renderbufferId)); + if (attachment == DEPTH_STENCIL_ATTACHMENT) { + GLint secondaryId = m_isCombinedDepthStencilSupported + ? renderbufferId + : (renderbuffer ? renderbuffer->secondaryId() : 0); + m_commandQueue->queueCommand(CanvasGlCommandQueue::glFramebufferRenderbuffer, + GLint(GL_FRAMEBUFFER), GLint(DEPTH_ATTACHMENT), + GLint(GL_RENDERBUFFER), + GLint(renderbufferId)); + m_commandQueue->queueCommand(CanvasGlCommandQueue::glFramebufferRenderbuffer, + GLint(GL_FRAMEBUFFER), GLint(STENCIL_ATTACHMENT), + GLint(GL_RENDERBUFFER), + GLint(secondaryId)); + } else { + m_commandQueue->queueCommand(CanvasGlCommandQueue::glFramebufferRenderbuffer, + GLint(GL_FRAMEBUFFER), GLint(attachment), + GLint(GL_RENDERBUFFER), + GLint(renderbufferId)); + } } /*! @@ -1835,7 +1914,7 @@ void CanvasContext::framebufferTexture2D(glEnums target, glEnums attachment, glE CanvasTexture *texture = getAsTexture3D(texture3D); if (texture) { - if (!checkParent(texture, __FUNCTION__)) + if (!checkValidity(texture, __FUNCTION__)) return; if (textarget != TEXTURE_2D && textarget != TEXTURE_CUBE_MAP_POSITIVE_X @@ -1864,11 +1943,13 @@ void CanvasContext::framebufferTexture2D(glEnums target, glEnums attachment, glE } } - GLint textureId = texture ? texture->textureId() : 0; - m_currentFramebuffer->setTexture(texture); - m_commandQueue->queueCommand(CanvasGlCommandQueue::glFramebufferTexture2D, - GLint(target), GLint(attachment), - GLint(textarget), textureId, GLint(level)); + if (!checkContextLost()) { + GLint textureId = texture ? texture->textureId() : 0; + m_currentFramebuffer->setTexture(texture); + m_commandQueue->queueCommand(CanvasGlCommandQueue::glFramebufferTexture2D, + GLint(target), GLint(attachment), + GLint(textarget), textureId, GLint(level)); + } } /*! @@ -1884,7 +1965,7 @@ bool CanvasContext::isFramebuffer(QJSValue anyObject) << ")"; CanvasFrameBuffer *fbo = getAsFramebuffer(anyObject); - if (fbo && checkParent(fbo, __FUNCTION__)) { + if (fbo && checkValidity(fbo, __FUNCTION__)) { GLboolean boolValue; GlSyncCommand syncCommand(CanvasGlCommandQueue::glIsFramebuffer, fbo->id()); syncCommand.returnValue = &boolValue; @@ -1922,7 +2003,7 @@ void CanvasContext::deleteFramebuffer(QJSValue buffer) CanvasFrameBuffer *fbo = getAsFramebuffer(buffer); if (fbo) { - if (!checkParent(fbo, __FUNCTION__)) + if (!checkValidity(fbo, __FUNCTION__)) return; fbo->del(); } else { @@ -1939,10 +2020,15 @@ void CanvasContext::deleteFramebuffer(QJSValue buffer) */ QJSValue CanvasContext::createRenderbuffer() { - CanvasRenderBuffer *renderbuffer = new CanvasRenderBuffer(m_commandQueue, this); + if (checkContextLost()) + return QJSValue(QJSValue::NullValue); + + CanvasRenderBuffer *renderbuffer = + new CanvasRenderBuffer(m_commandQueue, !m_isCombinedDepthStencilSupported, this); QJSValue value = m_engine->newQObject(renderbuffer); qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "():" << value.toString(); + addObjectToValidList(renderbuffer); return value; } @@ -1967,14 +2053,16 @@ void CanvasContext::bindRenderbuffer(glEnums target, QJSValue renderbuffer3D) CanvasRenderBuffer *renderbuffer = getAsRenderbuffer3D(renderbuffer3D); GLint bindId = 0; - if (renderbuffer && checkParent(renderbuffer, __FUNCTION__)) { + if (renderbuffer && checkValidity(renderbuffer, __FUNCTION__)) { m_currentRenderbuffer = renderbuffer; bindId = renderbuffer->id(); } else { m_currentRenderbuffer = 0; } - m_commandQueue->queueCommand(CanvasGlCommandQueue::glBindRenderbuffer, - GLint(GL_RENDERBUFFER), bindId); + if (!checkContextLost()) { + m_commandQueue->queueCommand(CanvasGlCommandQueue::glBindRenderbuffer, + GLint(GL_RENDERBUFFER), bindId); + } } /*! @@ -1983,7 +2071,7 @@ void CanvasContext::bindRenderbuffer(glEnums target, QJSValue renderbuffer3D) * \a target must be \c Context3D.RENDERBUFFER. * \a internalformat specifies the color-renderable, depth-renderable or stencil-renderable format * of the renderbuffer. Must be one of \c{Context3D.RGBA4}, \c{Context3D.RGB565}, \c{Context3D.RGB5_A1}, - * \c{Context3D.DEPTH_COMPONENT16} or \c{Context3D.STENCIL_INDEX8}. + * \c{Context3D.DEPTH_COMPONENT16}, \c{Context3D.STENCIL_INDEX8}, or \c{Context3D.DEPTH_STENCIL}. * \a width specifies the renderbuffer width in pixels. * \a height specifies the renderbuffer height in pixels. */ @@ -1997,6 +2085,9 @@ void CanvasContext::renderbufferStorage(glEnums target, glEnums internalformat, << ", height:" << height << ")"; + if (checkContextLost()) + return; + if (target != RENDERBUFFER) { qCWarning(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << ": INVALID_ENUM target must be RENDERBUFFER"; @@ -2004,9 +2095,38 @@ void CanvasContext::renderbufferStorage(glEnums target, glEnums internalformat, return; } - m_commandQueue->queueCommand(CanvasGlCommandQueue::glRenderbufferStorage, - GLint(target), GLint(internalformat), - GLint(width), GLint(height)); + if (!m_currentRenderbuffer) { + qCWarning(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ + << ": INVALID_OPERATION no renderbuffer bound"; + m_error |= CANVAS_INVALID_OPERATION; + return; + } + + if (internalformat == DEPTH_STENCIL) { + if (m_isCombinedDepthStencilSupported) { + m_commandQueue->queueCommand(CanvasGlCommandQueue::glRenderbufferStorage, + GLint(target), GLint(GL_DEPTH24_STENCIL8), + GLint(width), GLint(height)); + } else { + // Some platforms do not support combined DEPTH_STENCIL buffer natively, so create + // two separate render buffers for them. Depth buffer is the primary buffer + // and the stencil buffer is the secondary buffer. + m_commandQueue->queueCommand(CanvasGlCommandQueue::glRenderbufferStorage, + GLint(target), GLint(GL_DEPTH_COMPONENT16), + GLint(width), GLint(height)); + m_commandQueue->queueCommand(CanvasGlCommandQueue::glBindRenderbuffer, + GLint(target), m_currentRenderbuffer->secondaryId()); + m_commandQueue->queueCommand(CanvasGlCommandQueue::glRenderbufferStorage, + GLint(target), GLint(GL_STENCIL_INDEX8), + GLint(width), GLint(height)); + m_commandQueue->queueCommand(CanvasGlCommandQueue::glBindRenderbuffer, + GLint(target), m_currentRenderbuffer->id()); + } + } else { + m_commandQueue->queueCommand(CanvasGlCommandQueue::glRenderbufferStorage, + GLint(target), GLint(internalformat), + GLint(width), GLint(height)); + } } /*! @@ -2022,7 +2142,7 @@ bool CanvasContext::isRenderbuffer(QJSValue anyObject) << ")"; CanvasRenderBuffer *rbo = getAsRenderbuffer3D(anyObject); - if (rbo && checkParent(rbo, __FUNCTION__)) { + if (rbo && checkValidity(rbo, __FUNCTION__)) { GLboolean boolValue; GlSyncCommand syncCommand(CanvasGlCommandQueue::glIsRenderbuffer, rbo->id()); syncCommand.returnValue = &boolValue; @@ -2059,7 +2179,7 @@ void CanvasContext::deleteRenderbuffer(QJSValue renderbuffer3D) CanvasRenderBuffer *renderbuffer = getAsRenderbuffer3D(renderbuffer3D); if (renderbuffer) { - if (!checkParent(renderbuffer, __FUNCTION__)) + if (!checkValidity(renderbuffer, __FUNCTION__)) return; renderbuffer->del(); } else { @@ -2082,6 +2202,9 @@ void CanvasContext::sampleCoverage(float value, bool invert) << "(value:" << value << ", invert:" << invert << ")"; + if (checkContextLost()) + return; + m_commandQueue->queueCommand(CanvasGlCommandQueue::glSampleCoverage, GLint(invert), GLfloat(value)); } @@ -2093,11 +2216,14 @@ void CanvasContext::sampleCoverage(float value, bool invert) */ QJSValue CanvasContext::createProgram() { + if (checkContextLost()) + return QJSValue(QJSValue::NullValue); + CanvasProgram *program = new CanvasProgram(m_commandQueue, this); QJSValue value = m_engine->newQObject(program); qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "():" << value.toString(); - + addObjectToValidList(program); return value; } @@ -2115,7 +2241,7 @@ bool CanvasContext::isProgram(QJSValue anyObject) CanvasProgram *program = getAsProgram3D(anyObject); - if (program && checkParent(program, __FUNCTION__)) { + if (program && checkValidity(program, __FUNCTION__)) { GLboolean boolValue; GlSyncCommand syncCommand(CanvasGlCommandQueue::glIsProgram, program->id()); syncCommand.returnValue = &boolValue; @@ -2153,7 +2279,7 @@ void CanvasContext::deleteProgram(QJSValue program3D) CanvasProgram *program = getAsProgram3D(program3D, true); if (program) { - if (!checkParent(program, __FUNCTION__)) + if (!checkValidity(program, __FUNCTION__)) return; program->del(); } else { @@ -2195,7 +2321,7 @@ void CanvasContext::attachShader(QJSValue program3D, QJSValue shader3D) return; } - if (!checkParent(program, __FUNCTION__) || !checkParent(shader, __FUNCTION__)) + if (!checkValidity(program, __FUNCTION__) || !checkValidity(shader, __FUNCTION__)) return; program->attach(shader); @@ -2220,7 +2346,7 @@ QJSValue CanvasContext::getAttachedShaders(QJSValue program3D) return QJSValue(QJSValue::NullValue); } - if (!checkParent(program, __FUNCTION__)) + if (!checkValidity(program, __FUNCTION__)) return QJSValue(QJSValue::NullValue); QList<CanvasShader *> shaders = program->attachedShaders(); @@ -2269,7 +2395,7 @@ void CanvasContext::detachShader(QJSValue program3D, QJSValue shader3D) return; } - if (!checkParent(program, __FUNCTION__) || !checkParent(shader, __FUNCTION__)) + if (!checkValidity(program, __FUNCTION__) || !checkValidity(shader, __FUNCTION__)) return; program->detach(shader); @@ -2288,7 +2414,7 @@ void CanvasContext::linkProgram(QJSValue program3D) CanvasProgram *program = getAsProgram3D(program3D); - if (!program || !checkParent(program, __FUNCTION__)) { + if (!program || !checkValidity(program, __FUNCTION__)) { m_error |= CANVAS_INVALID_OPERATION; return; } @@ -2306,6 +2432,9 @@ void CanvasContext::lineWidth(float width) qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "(width:" << width << ")"; + if (checkContextLost()) + return; + m_commandQueue->queueCommand(CanvasGlCommandQueue::glLineWidth, GLfloat(width)); } @@ -2323,6 +2452,9 @@ void CanvasContext::polygonOffset(float factor, float units) << "(factor:" << factor << ", units:" << units << ")"; + if (checkContextLost()) + return; + m_commandQueue->queueCommand(CanvasGlCommandQueue::glPolygonOffset, GLfloat(factor), GLfloat(units)); } @@ -2348,6 +2480,9 @@ void CanvasContext::pixelStorei(glEnums pname, int param) << ", param:" << param << ")"; + if (checkContextLost()) + return; + switch (pname) { case UNPACK_FLIP_Y_WEBGL: m_unpackFlipYEnabled = (param != 0); @@ -2384,6 +2519,9 @@ void CanvasContext::hint(glEnums target, glEnums mode) << "(target:" << glEnumToString(target) << ",mode:" << glEnumToString(mode) << ")"; + if (checkContextLost()) + return; + switch (target) { case FRAGMENT_SHADER_DERIVATIVE_HINT_OES: if (!m_standardDerivatives) { @@ -2485,6 +2623,9 @@ void CanvasContext::blendColor(float red, float green, float blue, float alpha) << ", alpha:" << alpha << ")"; + if (checkContextLost()) + return; + m_commandQueue->queueCommand(CanvasGlCommandQueue::glBlendColor, GLfloat(red), GLfloat(green), GLfloat(blue), GLfloat(alpha)); @@ -2492,6 +2633,9 @@ void CanvasContext::blendColor(float red, float green, float blue, float alpha) bool CanvasContext::checkBlendMode(glEnums mode) { + if (checkContextLost()) + return false; + switch (mode) { case FUNC_ADD: case FUNC_SUBTRACT: @@ -2574,6 +2718,9 @@ void CanvasContext::blendFunc(glEnums sfactor, glEnums dfactor) << ", dfactor:" << glEnumToString(dfactor) << ")"; + if (checkContextLost()) + return; + if (((sfactor == CONSTANT_COLOR || sfactor == ONE_MINUS_CONSTANT_COLOR) && (dfactor == CONSTANT_ALPHA || dfactor == ONE_MINUS_CONSTANT_ALPHA)) || ((dfactor == CONSTANT_COLOR || dfactor == ONE_MINUS_CONSTANT_COLOR) @@ -2634,6 +2781,9 @@ void CanvasContext::blendFuncSeparate(glEnums srcRGB, glEnums dstRGB, glEnums sr << ", dstAlpha:" << glEnumToString(dstAlpha) << ")"; + if (checkContextLost()) + return; + if (((srcRGB == CONSTANT_COLOR || srcRGB == ONE_MINUS_CONSTANT_COLOR ) && (dstRGB == CONSTANT_ALPHA || dstRGB == ONE_MINUS_CONSTANT_ALPHA )) || ((dstRGB == CONSTANT_COLOR || dstRGB == ONE_MINUS_CONSTANT_COLOR ) @@ -2667,7 +2817,7 @@ QJSValue CanvasContext::getProgramParameter(QJSValue program3D, glEnums paramNam CanvasProgram *program = getAsProgram3D(program3D); - if (!program || !checkParent(program, __FUNCTION__)) { + if (!program || !checkValidity(program, __FUNCTION__)) { m_error |= CANVAS_INVALID_OPERATION; return QJSValue(QJSValue::NullValue); } @@ -2721,19 +2871,26 @@ QJSValue CanvasContext::getProgramParameter(QJSValue program3D, glEnums paramNam */ QJSValue CanvasContext::createShader(glEnums type) { + if (checkContextLost()) + return QJSValue(QJSValue::NullValue); + switch (type) { case VERTEX_SHADER: // Intentional fall through - case FRAGMENT_SHADER: + case FRAGMENT_SHADER: { qCDebug(canvas3drendering).nospace() << "Context3D::createShader(" << glEnumToString(type) << ")"; - return m_engine->newQObject(new CanvasShader(GLenum(type), m_commandQueue, this)); - default: + CanvasShader *shader = new CanvasShader(GLenum(type), m_commandQueue, this); + addObjectToValidList(shader); + return m_engine->newQObject(shader); + } + default: { qCWarning(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << ":INVALID_ENUM:unknown shader type:" << glEnumToString(type); m_error |= CANVAS_INVALID_ENUM; return QJSValue(QJSValue::NullValue); } + } } /*! @@ -2749,7 +2906,7 @@ bool CanvasContext::isShader(QJSValue anyObject) << ")"; CanvasShader *shader3D = getAsShader3D(anyObject); - if (shader3D && checkParent(shader3D, __FUNCTION__)) { + if (shader3D && checkValidity(shader3D, __FUNCTION__)) { GLboolean boolValue; GlSyncCommand syncCommand(CanvasGlCommandQueue::glIsShader, shader3D->id()); syncCommand.returnValue = &boolValue; @@ -2788,7 +2945,7 @@ void CanvasContext::deleteShader(QJSValue shader3D) CanvasShader *shader = getAsShader3D(shader3D, true); if (shader) { - if (!checkParent(shader, __FUNCTION__)) + if (!checkValidity(shader, __FUNCTION__)) return; shader->del(); } else { @@ -2825,7 +2982,7 @@ void CanvasContext::shaderSource(QJSValue shader3D, const QString &shaderSource) << "Invalid shader handle:" << shader3D.toString(); return; } - if (!checkParent(shader, __FUNCTION__)) + if (!checkValidity(shader, __FUNCTION__)) return; shader->setSourceCode(modSource); @@ -2850,7 +3007,7 @@ QJSValue CanvasContext::getShaderSource(QJSValue shader3D) << "Invalid shader handle:" << shader3D.toString(); return QJSValue(QJSValue::NullValue); } - if (!checkParent(shader, __FUNCTION__)) + if (!checkValidity(shader, __FUNCTION__)) return false; return QJSValue(shader->sourceCode()); @@ -2873,7 +3030,7 @@ void CanvasContext::compileShader(QJSValue shader3D) << "Invalid shader handle:" << shader3D.toString(); return; } - if (!checkParent(shader, __FUNCTION__)) + if (!checkValidity(shader, __FUNCTION__)) return; shader->compileShader(); @@ -3048,6 +3205,9 @@ void CanvasContext::vertexAttribNfv(int dim, unsigned int indx, const QJSValue & << ")"; } + if (checkContextLost()) + return; + CanvasGlCommandQueue::GlCommandId id(CanvasGlCommandQueue::internalNoCommand); switch (dim) { case 1: @@ -3118,7 +3278,7 @@ void CanvasContext::uniformNf(int dim, const QJSValue &location, float x, float CanvasUniformLocation *locationObj = getAsUniformLocation3D(location); - if (!locationObj || !checkParent(locationObj, __FUNCTION__)) { + if (!locationObj || !checkValidity(locationObj, __FUNCTION__)) { m_error |= CANVAS_INVALID_OPERATION; return; } @@ -3167,7 +3327,7 @@ void CanvasContext::uniformNi(int dim, const QJSValue &location, int x, int y, i CanvasUniformLocation *locationObj = getAsUniformLocation3D(location); - if (!locationObj || !checkParent(locationObj, __FUNCTION__)) { + if (!locationObj || !checkValidity(locationObj, __FUNCTION__)) { m_error |= CANVAS_INVALID_OPERATION; return; } @@ -3228,7 +3388,7 @@ void CanvasContext::uniformNxv(int dim, bool typeFloat, const QJSValue &location } CanvasUniformLocation *locationObj = getAsUniformLocation3D(location); - if (!locationObj || !checkParent(locationObj, __FUNCTION__)) { + if (!locationObj || !checkValidity(locationObj, __FUNCTION__)) { m_error |= CANVAS_INVALID_OPERATION; return; } @@ -3282,25 +3442,27 @@ void CanvasContext::uniformNxv(int dim, bool typeFloat, const QJSValue &location bool CanvasContext::isCapabilityValid(CanvasContext::glEnums cap) { bool capValid = false; - switch (GLint(cap)) { - case CULL_FACE: - case BLEND: - case DITHER: - case STENCIL_TEST: - case DEPTH_TEST: - case SCISSOR_TEST: - case POLYGON_OFFSET_FILL: - case SAMPLE_ALPHA_TO_COVERAGE: - case SAMPLE_COVERAGE: - capValid = true; - break; - default: - qCWarning(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ - << ":INVALID_ENUM:" - << "Tried to enable, disable, or query an invalid capability:" - << glEnumToString(cap); - m_error |= CANVAS_INVALID_ENUM; - break; + if (!checkContextLost()) { + switch (GLint(cap)) { + case CULL_FACE: + case BLEND: + case DITHER: + case STENCIL_TEST: + case DEPTH_TEST: + case SCISSOR_TEST: + case POLYGON_OFFSET_FILL: + case SAMPLE_ALPHA_TO_COVERAGE: + case SAMPLE_COVERAGE: + capValid = true; + break; + default: + qCWarning(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ + << ":INVALID_ENUM:" + << "Tried to enable, disable, or query an invalid capability:" + << glEnumToString(cap); + m_error |= CANVAS_INVALID_ENUM; + break; + } } return capValid; } @@ -3316,6 +3478,8 @@ void CanvasContext::vertexAttrib1f(unsigned int indx, float x) << "(indx:" << indx << ", x:" << x << ")"; + if (checkContextLost()) + return; m_commandQueue->queueCommand(CanvasGlCommandQueue::glVertexAttrib1f, GLint(indx), GLfloat(x)); } @@ -3342,6 +3506,8 @@ void CanvasContext::vertexAttrib2f(unsigned int indx, float x, float y) << ", x:" << x << ", y:" << y << ")"; + if (checkContextLost()) + return; m_commandQueue->queueCommand(CanvasGlCommandQueue::glVertexAttrib2f, GLint(indx), GLfloat(x), GLfloat(y)); @@ -3370,6 +3536,8 @@ void CanvasContext::vertexAttrib3f(unsigned int indx, float x, float y, float z) << ", y:" << y << ", z:" << z << ")"; + if (checkContextLost()) + return; m_commandQueue->queueCommand(CanvasGlCommandQueue::glVertexAttrib3f, GLint(indx), GLfloat(x), GLfloat(y), GLfloat(z)); @@ -3399,6 +3567,8 @@ void CanvasContext::vertexAttrib4f(unsigned int indx, float x, float y, float z, << ", z:" << z << ", w:" << w << ")"; + if (checkContextLost()) + return; m_commandQueue->queueCommand(CanvasGlCommandQueue::glVertexAttrib4f, GLint(indx), GLfloat(x), GLfloat(y), GLfloat(z), GLfloat(w)); @@ -3435,7 +3605,7 @@ QJSValue CanvasContext::getShaderParameter(QJSValue shader3D, glEnums pname) m_error |= CANVAS_INVALID_OPERATION; return QJSValue(QJSValue::NullValue); } - if (!checkParent(shader, __FUNCTION__)) + if (!checkValidity(shader, __FUNCTION__)) return QJSValue(QJSValue::NullValue); GlSyncCommand syncCommand(CanvasGlCommandQueue::glGetShaderiv, @@ -3480,12 +3650,16 @@ QJSValue CanvasContext::getShaderParameter(QJSValue shader3D, glEnums pname) */ QJSValue CanvasContext::createBuffer() { + if (checkContextLost()) + return QJSValue(QJSValue::NullValue); + CanvasBuffer *newBuffer = new CanvasBuffer(m_commandQueue, this); m_idToCanvasBufferMap.insert(newBuffer->id(), newBuffer); QJSValue value = m_engine->newQObject(newBuffer); qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << ":" << value.toString() << " = " << newBuffer; + addObjectToValidList(newBuffer); return value; } @@ -3508,7 +3682,7 @@ QJSValue CanvasContext::getUniformLocation(QJSValue program3D, const QString &na m_error |= CANVAS_INVALID_OPERATION; return QJSValue(QJSValue::NullValue); } - if (!checkParent(program, __FUNCTION__)) + if (!checkValidity(program, __FUNCTION__)) return QJSValue(QJSValue::NullValue); CanvasUniformLocation *location3D = new CanvasUniformLocation(m_commandQueue, this); @@ -3518,6 +3692,7 @@ QJSValue CanvasContext::getUniformLocation(QJSValue program3D, const QString &na << "(program3D:" << program3D.toString() << ", name:" << value.toString() << "):" << location3D; + addObjectToValidList(location3D); GlCommand &command = m_commandQueue->queueCommand(CanvasGlCommandQueue::glGetUniformLocation, location3D->id(), program->id()); @@ -3534,6 +3709,9 @@ QJSValue CanvasContext::getUniformLocation(QJSValue program3D, const QString &na */ int CanvasContext::getAttribLocation(QJSValue program3D, const QString &name) { + if (checkContextLost()) + return -1; + CanvasProgram *program = getAsProgram3D(program3D); if (!program) { qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ @@ -3545,7 +3723,7 @@ int CanvasContext::getAttribLocation(QJSValue program3D, const QString &name) m_error |= CANVAS_INVALID_OPERATION; return -1; } else { - if (!checkParent(program, __FUNCTION__)) + if (!checkValidity(program, __FUNCTION__)) return -1; GLint attribLoc = -1; GlSyncCommand syncCommand(CanvasGlCommandQueue::glGetAttribLocation, program->id()); @@ -3584,7 +3762,7 @@ void CanvasContext::bindAttribLocation(QJSValue program3D, int index, const QStr m_error |= CANVAS_INVALID_OPERATION; return; } - if (!checkParent(program, __FUNCTION__)) + if (!checkValidity(program, __FUNCTION__)) return; program->bindAttributeLocation(index, name); @@ -3599,6 +3777,8 @@ void CanvasContext::enableVertexAttribArray(int index) qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "(index:" << index << ")"; + if (checkContextLost()) + return; m_commandQueue->queueCommand(CanvasGlCommandQueue::glEnableVertexAttribArray, GLint(index)); } @@ -3612,6 +3792,8 @@ void CanvasContext::disableVertexAttribArray(int index) qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "(index:" << index << ")"; + if (checkContextLost()) + return; m_commandQueue->queueCommand(CanvasGlCommandQueue::glDisableVertexAttribArray, GLint(index)); } @@ -3673,6 +3855,8 @@ void CanvasContext::vertexAttribPointer(int indx, int size, glEnums type, << ", stride:" << stride << ", offset:" << offset << ")"; + if (checkContextLost()) + return; if (!m_currentArrayBuffer) { qCWarning(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ @@ -3966,7 +4150,7 @@ bool CanvasContext::isBuffer(QJSValue anyObject) << ")"; CanvasBuffer *buffer = getAsBuffer3D(anyObject); - if (buffer && checkParent(buffer, __FUNCTION__)) { + if (buffer && checkValidity(buffer, __FUNCTION__)) { GLboolean boolValue; GlSyncCommand syncCommand(CanvasGlCommandQueue::glIsBuffer, buffer->id()); syncCommand.returnValue = &boolValue; @@ -4007,7 +4191,7 @@ void CanvasContext::deleteBuffer(QJSValue buffer3D) m_error |= CANVAS_INVALID_OPERATION; return; } - if (!checkParent(bufferObj, __FUNCTION__)) + if (!checkValidity(bufferObj, __FUNCTION__)) return; m_idToCanvasBufferMap.remove(bufferObj->id()); @@ -4023,33 +4207,40 @@ CanvasContext::glEnums CanvasContext::getError() { qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__; - // Fetch GL errors synchronously - GlSyncCommand syncCommand(CanvasGlCommandQueue::glGetError); - int canvasError = CANVAS_NO_ERRORS; - syncCommand.returnValue = &canvasError; - scheduleSyncCommand(&syncCommand); - - m_error |= canvasError; - glEnums retVal = NO_ERROR; - if (m_error != CANVAS_NO_ERRORS) { - // Return set error flags one by one and clear the flags. - // Note that stack overflow/underflow flags are never returned. - if ((m_error & CANVAS_INVALID_ENUM) != 0) { - retVal = INVALID_ENUM; - m_error &= ~(CANVAS_INVALID_ENUM); - } else if ((m_error & CANVAS_INVALID_VALUE) != 0) { - retVal = INVALID_VALUE; - m_error &= ~(CANVAS_INVALID_VALUE); - }else if ((m_error & CANVAS_INVALID_OPERATION) != 0) { - retVal = INVALID_OPERATION; - m_error &= ~(CANVAS_INVALID_OPERATION); - } else if ((m_error & CANVAS_OUT_OF_MEMORY) != 0) { - retVal = OUT_OF_MEMORY; - m_error &= ~(CANVAS_OUT_OF_MEMORY); - } else if ((m_error & CANVAS_INVALID_FRAMEBUFFER_OPERATION) != 0) { - retVal = INVALID_FRAMEBUFFER_OPERATION; - m_error &= ~(CANVAS_INVALID_FRAMEBUFFER_OPERATION); + if (m_contextLost) { + if (!m_contextLostErrorReported) { + m_contextLostErrorReported = true; + retVal = CONTEXT_LOST_WEBGL; + } + } else { + // Fetch GL errors synchronously + GlSyncCommand syncCommand(CanvasGlCommandQueue::glGetError); + int canvasError = CANVAS_NO_ERRORS; + syncCommand.returnValue = &canvasError; + scheduleSyncCommand(&syncCommand); + + m_error |= canvasError; + + if (m_error != CANVAS_NO_ERRORS) { + // Return set error flags one by one and clear the flags. + // Note that stack overflow/underflow flags are never returned. + if ((m_error & CANVAS_INVALID_ENUM) != 0) { + retVal = INVALID_ENUM; + m_error &= ~(CANVAS_INVALID_ENUM); + } else if ((m_error & CANVAS_INVALID_VALUE) != 0) { + retVal = INVALID_VALUE; + m_error &= ~(CANVAS_INVALID_VALUE); + }else if ((m_error & CANVAS_INVALID_OPERATION) != 0) { + retVal = INVALID_OPERATION; + m_error &= ~(CANVAS_INVALID_OPERATION); + } else if ((m_error & CANVAS_OUT_OF_MEMORY) != 0) { + retVal = OUT_OF_MEMORY; + m_error &= ~(CANVAS_OUT_OF_MEMORY); + } else if ((m_error & CANVAS_INVALID_FRAMEBUFFER_OPERATION) != 0) { + retVal = INVALID_FRAMEBUFFER_OPERATION; + m_error &= ~(CANVAS_INVALID_FRAMEBUFFER_OPERATION); + } } } @@ -4066,6 +4257,8 @@ QJSValue CanvasContext::getParameter(glEnums pname) qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "( pname:" << glEnumToString(pname) << ")"; + if (checkContextLost()) + return QJSValue(QJSValue::NullValue); GLint value = 0; GlSyncCommand syncCommand(CanvasGlCommandQueue::glGetIntegerv, GLint(pname)); @@ -4390,7 +4583,7 @@ QJSValue CanvasContext::getShaderInfoLog(QJSValue shader3D) m_error |= CANVAS_INVALID_OPERATION; return QJSValue(QJSValue::NullValue); } - if (!checkParent(shader, __FUNCTION__)) + if (!checkValidity(shader, __FUNCTION__)) return QJSValue(QJSValue::NullValue); QString log; @@ -4423,7 +4616,7 @@ QJSValue CanvasContext::getProgramInfoLog(QJSValue program3D) m_error |= CANVAS_INVALID_OPERATION; return QJSValue(QJSValue::NullValue); } - if (!checkParent(program, __FUNCTION__)) + if (!checkValidity(program, __FUNCTION__)) return QJSValue(QJSValue::NullValue); QString log; @@ -4459,7 +4652,7 @@ void CanvasContext::bindBuffer(glEnums target, QJSValue buffer3D) } CanvasBuffer *buffer = getAsBuffer3D(buffer3D); - if (buffer && checkParent(buffer, __FUNCTION__)) { + if (buffer && checkValidity(buffer, __FUNCTION__)) { if (target == ARRAY_BUFFER) { if (buffer->target() == CanvasBuffer::UNINITIALIZED) buffer->setTarget(CanvasBuffer::ARRAY_BUFFER); @@ -4511,7 +4704,7 @@ void CanvasContext::validateProgram(QJSValue program3D) m_error |= CANVAS_INVALID_OPERATION; return; } - if (checkParent(program, __FUNCTION__)) + if (checkValidity(program, __FUNCTION__)) program->validateProgram(); } @@ -4532,7 +4725,7 @@ void CanvasContext::useProgram(QJSValue program3D) return; } - if (!checkParent(program, __FUNCTION__)) + if (!checkValidity(program, __FUNCTION__)) return; program->useProgram(); } @@ -4558,6 +4751,9 @@ void CanvasContext::clear(glEnums flags) << "(flags:" << flagStr << ")"; } + if (checkContextLost()) + return; + m_commandQueue->queueCommand(CanvasGlCommandQueue::glClear, GLint(flags)); // Set clear flags if the clear targets default framebuffer @@ -4574,6 +4770,9 @@ void CanvasContext::cullFace(glEnums mode) { qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "(mode:" << glEnumToString(mode) << ")"; + if (checkContextLost()) + return; + m_commandQueue->queueCommand(CanvasGlCommandQueue::glCullFace, GLint(mode)); } @@ -4586,6 +4785,9 @@ void CanvasContext::frontFace(glEnums mode) { qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "(mode:" << glEnumToString(mode) << ")"; + if (checkContextLost()) + return; + m_commandQueue->queueCommand(CanvasGlCommandQueue::glFrontFace, GLint(mode)); } @@ -4597,6 +4799,9 @@ void CanvasContext::depthMask(bool flag) { qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "(flag:" << flag << ")"; + if (checkContextLost()) + return; + m_commandQueue->queueCommand(CanvasGlCommandQueue::glDepthMask, GLint(flag)); } @@ -4610,6 +4815,9 @@ void CanvasContext::depthFunc(glEnums func) { qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "(func:" << glEnumToString(func) << ")"; + if (checkContextLost()) + return; + m_commandQueue->queueCommand(CanvasGlCommandQueue::glDepthFunc, GLint(func)); } @@ -4623,6 +4831,8 @@ void CanvasContext::depthRange(float zNear, float zFar) qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "(zNear:" << zNear << ", zFar:" << zFar << ")"; + if (checkContextLost()) + return; m_commandQueue->queueCommand(CanvasGlCommandQueue::glDepthRangef, GLfloat(zNear), GLfloat(zFar)); @@ -4636,6 +4846,9 @@ void CanvasContext::clearStencil(int stencil) { qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "(stencil:" << stencil << ")"; + if (checkContextLost()) + return; + m_commandQueue->queueCommand(CanvasGlCommandQueue::glClearStencil, GLint(stencil)); } @@ -4651,6 +4864,8 @@ void CanvasContext::colorMask(bool maskRed, bool maskGreen, bool maskBlue, bool << ", maskGreen:" << maskGreen << ", maskBlue:" << maskBlue << ", maskAlpha:" << maskAlpha << ")"; + if (checkContextLost()) + return; m_commandQueue->queueCommand(CanvasGlCommandQueue::glColorMask, GLint(maskRed), GLint(maskGreen), @@ -4666,6 +4881,8 @@ void CanvasContext::clearDepth(float depth) { qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "(depth:" << depth << ")"; + if (checkContextLost()) + return; m_commandQueue->queueCommand(CanvasGlCommandQueue::glClearDepthf, GLfloat(depth)); } @@ -4682,6 +4899,8 @@ void CanvasContext::clearColor(float red, float green, float blue, float alpha) << ", green:" << green << ", blue:" << blue << ", alpha:" << alpha << ")"; + if (checkContextLost()) + return; m_commandQueue->queueCommand(CanvasGlCommandQueue::glClearColor, GLfloat(red), GLfloat(green), @@ -4704,6 +4923,8 @@ void CanvasContext::viewport(int x, int y, int width, int height) << ", y:" << y << ", width:" << width << ", height:" << height << ")"; + if (checkContextLost()) + return; m_commandQueue->queueCommand(CanvasGlCommandQueue::glViewport, GLint(x), GLint(y), GLint(width), GLint(height)); @@ -4729,6 +4950,8 @@ void CanvasContext::drawArrays(glEnums mode, int first, int count) << "(mode:" << glEnumToString(mode) << ", first:" << first << ", count:" << count << ")"; + if (checkContextLost()) + return; if (first < 0) { qCWarning(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ @@ -4829,6 +5052,9 @@ void CanvasContext::drawElements(glEnums mode, int count, glEnums type, long off void CanvasContext::readPixels(int x, int y, long width, long height, glEnums format, glEnums type, QJSValue pixels) { + if (checkContextLost()) + return; + if (format != RGBA) { qCWarning(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << ":INVALID_ENUM:format must be RGBA."; @@ -4881,7 +5107,7 @@ CanvasActiveInfo *CanvasContext::getActiveAttrib(QJSValue program3D, uint index) << ", index:" << index << ")"; CanvasProgram *program = getAsProgram3D(program3D); - if (!program || !checkParent(program, __FUNCTION__)) { + if (!program || !checkValidity(program, __FUNCTION__)) { m_error |= CANVAS_INVALID_OPERATION; return 0; } @@ -4916,7 +5142,7 @@ CanvasActiveInfo *CanvasContext::getActiveUniform(QJSValue program3D, uint index << ", index:" << index << ")"; CanvasProgram *program = getAsProgram3D(program3D); - if (!program || !checkParent(program, __FUNCTION__)) { + if (!program || !checkValidity(program, __FUNCTION__)) { m_error |= CANVAS_INVALID_OPERATION; return 0; } @@ -4960,6 +5186,9 @@ void CanvasContext::stencilFunc(glEnums func, int ref, uint mask) << ", ref:" << ref << ", mask:" << mask << ")"; + if (checkContextLost()) + return; + // Clamp ref if (ref < 0) ref = 0; @@ -4998,6 +5227,9 @@ void CanvasContext::stencilFuncSeparate(glEnums face, glEnums func, int ref, uin << ", ref:" << ref << ", mask:" << mask << ")"; + if (checkContextLost()) + return; + // Clamp ref if (ref < 0) ref = 0; @@ -5016,6 +5248,8 @@ void CanvasContext::stencilMask(uint mask) qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "(mask:" << mask << ")"; + if (checkContextLost()) + return; m_commandQueue->queueCommand(CanvasGlCommandQueue::glStencilMask, GLint(mask)); } @@ -5037,6 +5271,8 @@ void CanvasContext::stencilMaskSeparate(glEnums face, uint mask) << "(face:" << glEnumToString(face) << ", mask:" << mask << ")"; + if (checkContextLost()) + return; m_commandQueue->queueCommand(CanvasGlCommandQueue::glStencilMaskSeparate, GLint(face), GLint(mask)); @@ -5065,6 +5301,8 @@ void CanvasContext::stencilOp(glEnums sfail, glEnums zfail, glEnums zpass) << ", zfail:" << glEnumToString(zfail) << ", zpass:" << glEnumToString(zpass) << ")"; + if (checkContextLost()) + return; m_commandQueue->queueCommand(CanvasGlCommandQueue::glStencilOp, GLint(sfail), GLint(zfail), GLint(zpass)); @@ -5100,6 +5338,8 @@ void CanvasContext::stencilOpSeparate(glEnums face, glEnums fail, glEnums zfail, << ", zfail:" << glEnumToString(zfail) << ", zpass:" << glEnumToString(zpass) << ")"; + if (checkContextLost()) + return; m_commandQueue->queueCommand(CanvasGlCommandQueue::glStencilOpSeparate, GLint(face), GLint(fail), GLint(zfail), GLint(zpass)); @@ -5119,6 +5359,9 @@ QJSValue CanvasContext::getFramebufferAttachmentParameter(glEnums target, glEnum << ", attachment:" << glEnumToString(attachment) << ", pname:" << glEnumToString(pname) << ")"; + if (checkContextLost()) + return QJSValue(QJSValue::NullValue); + if (target != FRAMEBUFFER) { qCWarning(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << ":INVALID_ENUM:" @@ -5193,6 +5436,8 @@ QJSValue CanvasContext::getRenderbufferParameter(glEnums target, glEnums pname) << "(target" << glEnumToString(target) << ", pname:" << glEnumToString(pname) << ")"; + if (checkContextLost()) + return QJSValue(QJSValue::NullValue); if (target != RENDERBUFFER) { qCWarning(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ @@ -5251,6 +5496,8 @@ QJSValue CanvasContext::getTexParameter(glEnums target, glEnums pname) << "(target" << glEnumToString(target) << ", pname:" << glEnumToString(pname) << ")"; + if (checkContextLost()) + return QJSValue(QJSValue::NullValue); GLint parameter = 0; if (isValidTextureBound(target, __FUNCTION__, false)) { @@ -5302,6 +5549,8 @@ uint CanvasContext::getVertexAttribOffset(uint index, glEnums pname) << "(index" << index << ", pname:" << glEnumToString(pname) << ")"; + if (checkContextLost()) + return 0; if (pname != VERTEX_ATTRIB_ARRAY_POINTER) { qCWarning(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ @@ -5366,6 +5615,8 @@ QJSValue CanvasContext::getVertexAttrib(uint index, glEnums pname) << "(index" << index << ", pname:" << glEnumToString(pname) << ")"; + if (checkContextLost()) + return QJSValue(QJSValue::NullValue); if (index >= MAX_VERTEX_ATTRIBS) { qCWarning(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ @@ -5439,10 +5690,15 @@ QJSValue CanvasContext::getVertexAttrib(uint index, glEnums pname) */ QJSValue CanvasContext::createTextureFromSource(QQuickItem *item) { + if (checkContextLost()) + return QJSValue(QJSValue::NullValue); + // First check if we have a CanvasTexture already for this item CanvasTexture *texture = m_quickItemToTextureMap.value(item, 0); - if (!texture) + if (!texture) { texture = new CanvasTexture(m_commandQueue, this, item); + addObjectToValidList(texture); + } m_quickItemToTextureMap.insert(item, texture); @@ -5547,7 +5803,7 @@ QJSValue CanvasContext::getUniform(QJSValue program3D, QJSValue location3D) << ":INVALID_OPERATION:No location3D was specified"; m_error |= CANVAS_INVALID_OPERATION; return QJSValue(QJSValue::UndefinedValue); - } else if (!checkParent(program, __FUNCTION__) || !checkParent(location, __FUNCTION__)) { + } else if (!checkValidity(program, __FUNCTION__) || !checkValidity(location, __FUNCTION__)) { return QJSValue(QJSValue::UndefinedValue); } @@ -5711,19 +5967,22 @@ QVariantList CanvasContext::getSupportedExtensions() qCDebug(canvas3drendering).nospace() << Q_FUNC_INFO; QVariantList list; - list.append(QVariant::fromValue(QStringLiteral("QTCANVAS3D_gl_state_dump"))); - if (!m_isOpenGLES2 || - (m_contextVersion >= 3 - || m_extensions.contains("OES_standard_derivatives"))) { - list.append(QVariant::fromValue(QStringLiteral("OES_standard_derivatives"))); - } + if (!checkContextLost()) { + list.append(QVariant::fromValue(QStringLiteral("QTCANVAS3D_gl_state_dump"))); + + if (!m_isOpenGLES2 || + (m_contextVersion >= 3 + || m_extensions.contains("OES_standard_derivatives"))) { + list.append(QVariant::fromValue(QStringLiteral("OES_standard_derivatives"))); + } - if (m_extensions.contains("GL_EXT_texture_compression_s3tc")) - list.append(QVariant::fromValue(QStringLiteral("WEBGL_compressed_texture_s3tc"))); + if (m_extensions.contains("GL_EXT_texture_compression_s3tc")) + list.append(QVariant::fromValue(QStringLiteral("WEBGL_compressed_texture_s3tc"))); - if (m_extensions.contains("IMG_texture_compression_pvrtc")) - list.append(QVariant::fromValue(QStringLiteral("WEBGL_compressed_texture_pvrtc"))); + if (m_extensions.contains("IMG_texture_compression_pvrtc")) + list.append(QVariant::fromValue(QStringLiteral("WEBGL_compressed_texture_pvrtc"))); + } return list; } @@ -5811,36 +6070,97 @@ QVariant CanvasContext::getExtension(const QString &name) qCDebug(canvas3drendering).nospace() << "Context3D::" << __FUNCTION__ << "(name:" << name << ")"; - - QString upperCaseName = name.toUpper(); - - if (upperCaseName == QStringLiteral("QTCANVAS3D_GL_STATE_DUMP")) { - if (!m_stateDumpExt) - m_stateDumpExt = new CanvasGLStateDump(this, m_isOpenGLES2, this); - return QVariant::fromValue(m_stateDumpExt); - } else if (upperCaseName == QStringLiteral("QTCANVAS3D_TEXTURE_PROVIDER")) { - if (!m_textureProviderExt) - m_textureProviderExt = new CanvasTextureProvider(this, this); - return QVariant::fromValue(m_textureProviderExt); - } else if (upperCaseName == QStringLiteral("OES_STANDARD_DERIVATIVES") && - m_extensions.contains("OES_standard_derivatives")) { - if (!m_standardDerivatives) - m_standardDerivatives = new QObject(this); - return QVariant::fromValue(m_standardDerivatives); - } else if (upperCaseName == QStringLiteral("WEBGL_COMPRESSED_TEXTURE_S3TC") && - m_extensions.contains("GL_EXT_texture_compression_s3tc")) { - if (!m_compressedTextureS3TC) - m_compressedTextureS3TC = new CompressedTextureS3TC(this); - return QVariant::fromValue(m_compressedTextureS3TC); - } else if (upperCaseName == QStringLiteral("WEBGL_COMPRESSED_TEXTURE_PVRTC") && - m_extensions.contains("IMG_texture_compression_pvrtc")) { - if (!m_compressedTexturePVRTC) - m_compressedTexturePVRTC = new CompressedTexturePVRTC(this); - return QVariant::fromValue(m_compressedTexturePVRTC); + if (!checkContextLost()) { + QString upperCaseName = name.toUpper(); + + if (upperCaseName == QStringLiteral("QTCANVAS3D_GL_STATE_DUMP")) { + if (!m_stateDumpExt) + m_stateDumpExt = new CanvasGLStateDump(this, m_isOpenGLES2, this); + return QVariant::fromValue(m_stateDumpExt); + } else if (upperCaseName == QStringLiteral("QTCANVAS3D_TEXTURE_PROVIDER")) { + if (!m_textureProviderExt) + m_textureProviderExt = new CanvasTextureProvider(this, this); + return QVariant::fromValue(m_textureProviderExt); + } else if (upperCaseName == QStringLiteral("OES_STANDARD_DERIVATIVES") && + m_extensions.contains("OES_standard_derivatives")) { + if (!m_standardDerivatives) + m_standardDerivatives = new QObject(this); + return QVariant::fromValue(m_standardDerivatives); + } else if (upperCaseName == QStringLiteral("WEBGL_COMPRESSED_TEXTURE_S3TC") && + m_extensions.contains("GL_EXT_texture_compression_s3tc")) { + if (!m_compressedTextureS3TC) + m_compressedTextureS3TC = new CompressedTextureS3TC(this); + return QVariant::fromValue(m_compressedTextureS3TC); + } else if (upperCaseName == QStringLiteral("WEBGL_COMPRESSED_TEXTURE_PVRTC") && + m_extensions.contains("IMG_texture_compression_pvrtc")) { + if (!m_compressedTexturePVRTC) + m_compressedTexturePVRTC = new CompressedTexturePVRTC(this); + return QVariant::fromValue(m_compressedTexturePVRTC); + } } return QVariant(QVariant::Int); } +void CanvasContext::setContextLostState(bool lost) +{ + if (lost != m_contextLost) { + m_contextLost = lost; + + // Clear errors on lost state change + m_error = CANVAS_NO_ERRORS; + + if (lost) { + foreach (CanvasAbstractObject *jsObj, m_validObjectMap.keys()) { + jsObj->setInvalidated(true); + disconnect(jsObj, &QObject::destroyed, this, &CanvasContext::handleObjectDeletion); + } + m_validObjectMap.clear(); + m_quickItemToTextureMap.clear(); + m_idToCanvasBufferMap.clear(); + + m_currentProgram = 0; + m_currentArrayBuffer = 0; + m_currentElementArrayBuffer = 0; + m_currentTexture2D = 0; + m_currentTextureCubeMap = 0; + m_currentFramebuffer = 0; + m_currentRenderbuffer = 0; + m_contextLostErrorReported = false; + } + } +} + +void CanvasContext::setCommandQueue(CanvasGlCommandQueue *queue) +{ + m_commandQueue = queue; + connect(m_commandQueue, &CanvasGlCommandQueue::queueFull, + this, &CanvasContext::handleFullCommandQueue, Qt::DirectConnection); +} + +void CanvasContext::markQuickTexturesDirty() +{ + if (m_quickItemToTextureMap.size()) { + QMap<QQuickItem *, CanvasTexture *>::iterator i = m_quickItemToTextureMap.begin(); + while (i != m_quickItemToTextureMap.end()) { + m_commandQueue->addQuickItemAsTexture(i.key(), i.value()->textureId()); + i++; + } + } +} + +void CanvasContext::handleObjectDeletion(QObject *obj) +{ + CanvasAbstractObject *jsObj = qobject_cast<CanvasAbstractObject *>(obj); + if (jsObj) + m_validObjectMap.remove(jsObj); +} + +void CanvasContext::addObjectToValidList(CanvasAbstractObject *jsObj) +{ + m_validObjectMap.insert(jsObj, 0); + connect(jsObj, &QObject::destroyed, this, &CanvasContext::handleObjectDeletion); +} + QT_CANVAS3D_END_NAMESPACE QT_END_NAMESPACE diff --git a/src/imports/qtcanvas3d/context3d_p.h b/src/imports/qtcanvas3d/context3d_p.h index ec3b4bcd87671d0d5755a9771348bfd0285f7fa4..165ec68271ea799aa0fb2a247bef11f187ab5d12 100644 --- a/src/imports/qtcanvas3d/context3d_p.h +++ b/src/imports/qtcanvas3d/context3d_p.h @@ -973,8 +973,9 @@ public: ENUM_AS_PROPERTY(UNPACK_COLORSPACE_CONVERSION_WEBGL) ENUM_AS_PROPERTY(BROWSER_DEFAULT_WEBGL) - CanvasContext(QQmlEngine *engine, bool isES2, int maxVertexAttribs, int contextVersion, const QSet<QByteArray> &extensions, - CanvasGlCommandQueue *commandQueue, QObject *parent = 0); + CanvasContext(QQmlEngine *engine, bool isES2, int maxVertexAttribs, int contextVersion, + const QSet<QByteArray> &extensions, CanvasGlCommandQueue *commandQueue, + bool isCombinedDepthStencilSupported, QObject *parent = 0); ~CanvasContext(); void setCanvas(Canvas *canvas); @@ -1193,15 +1194,22 @@ public: void scheduleSyncCommand(GlSyncCommand *command); -public slots: - void handleFullCommandQueue(); - void handleTextureIdResolved(QQuickItem *item); + void setContextLostState(bool lost); + + CanvasGlCommandQueue *commandQueue() { return m_commandQueue; } + void setCommandQueue(CanvasGlCommandQueue *queue); + void markQuickTexturesDirty(); signals: void canvasChanged(Canvas *canvas); void drawingBufferWidthChanged(); void drawingBufferHeightChanged(); +private slots: + void handleFullCommandQueue(); + void handleTextureIdResolved(QQuickItem *item); + void handleObjectDeletion(QObject *obj); + private: uchar *getTypedArrayAsRawDataPtr(const QJSValue &jsValue, int &byteLength, QV4::Heap::TypedArray::Type type); @@ -1230,7 +1238,7 @@ private: bool isOfType(const QJSValue &value, const char *classname) const; bool isValidTextureBound(glEnums target, const QString &funcName, bool singleLayer = true); - bool checkParent(QObject *jsObj, const char *function); + bool checkValidity(CanvasAbstractObject *jsObj, const char *function); float *transposeMatrix(int dim, int count, float *src); void uniformMatrixNfv(int dim, const QJSValue &location3D, bool transpose, @@ -1254,6 +1262,9 @@ private: bool checkBufferUsage(glEnums usage); bool checkTextureFormats(glEnums internalFormat, glEnums format); bool checkTextureTarget(glEnums target); + bool checkContextLost(); + + void addObjectToValidList(CanvasAbstractObject *jsObj); typedef enum { CANVAS_NO_ERRORS = 0, @@ -1290,19 +1301,14 @@ private: int m_contextVersion; float **m_vertexAttribPointers; bool m_isOpenGLES2; + bool m_isCombinedDepthStencilSupported; CanvasGlCommandQueue *m_commandQueue; // Not owned QMutex m_renderJobMutex; QWaitCondition m_renderJobCondition; QMap<QQuickItem *, CanvasTexture *> m_quickItemToTextureMap; - - bool invalidEnumFlag; - bool invalidValueFlag; - bool invalidOperationFlag; - bool invalidStackOverflowFlag; - bool invalidStackUnderflowFlag; - bool invalidOutOfMemoryFlag; - bool invalidFramebufferFlag; - bool invalidContextLostFlag; + bool m_contextLost; + QMap<CanvasAbstractObject *, int> m_validObjectMap; + bool m_contextLostErrorReported; // EXTENSIONS CanvasGLStateDump *m_stateDumpExt; diff --git a/src/imports/qtcanvas3d/doc/qtcanvas3d.qdocconf b/src/imports/qtcanvas3d/doc/qtcanvas3d.qdocconf index 20533f7378bbae9cf942f86cd3d49d47eb9adc4b..be8e805f06c5a935f83f42d7f52c4f590db5ddc7 100644 --- a/src/imports/qtcanvas3d/doc/qtcanvas3d.qdocconf +++ b/src/imports/qtcanvas3d/doc/qtcanvas3d.qdocconf @@ -11,7 +11,7 @@ imagedirs += images headerdirs += .. sourcedirs += .. -examplesinstallpath = canvas3d +examplesinstallpath = qtcanvas3d/canvas3d depends = qtcore qtgui qtqml qtquick qtquickcontrols qtquicklayouts qtdoc qtmultimedia diff --git a/src/imports/qtcanvas3d/framebuffer3d.cpp b/src/imports/qtcanvas3d/framebuffer3d.cpp index e89d998dbc92af8002b4a28d35dc8069d6e7c4e4..90e11b3253843a81ab5562da128ed7412e3e542e 100644 --- a/src/imports/qtcanvas3d/framebuffer3d.cpp +++ b/src/imports/qtcanvas3d/framebuffer3d.cpp @@ -43,6 +43,7 @@ QT_CANVAS3D_BEGIN_NAMESPACE * \qmltype Canvas3DFrameBuffer * \since QtCanvas3D 1.0 * \inqmlmodule QtCanvas3D + * \inherits Canvas3DAbstractObject * \brief Contains an OpenGL framebuffer. * * An uncreatable QML type that contains an OpenGL framebuffer object. You can get it by calling the @@ -54,9 +55,7 @@ CanvasFrameBuffer::CanvasFrameBuffer(CanvasGlCommandQueue *queue, QObject *paren m_framebufferId(queue->createResourceId()), m_texture(0) { - Q_ASSERT(m_commandQueue); - - m_commandQueue->queueCommand(CanvasGlCommandQueue::glGenFramebuffers, m_framebufferId); + queueCommand(CanvasGlCommandQueue::glGenFramebuffers, m_framebufferId); } CanvasFrameBuffer::~CanvasFrameBuffer() @@ -72,7 +71,7 @@ bool CanvasFrameBuffer::isAlive() void CanvasFrameBuffer::del() { if (m_framebufferId) { - m_commandQueue->queueCommand(CanvasGlCommandQueue::glDeleteFramebuffers, m_framebufferId); + queueCommand(CanvasGlCommandQueue::glDeleteFramebuffers, m_framebufferId); m_framebufferId = 0; } } diff --git a/src/imports/qtcanvas3d/plugins.qmltypes b/src/imports/qtcanvas3d/plugins.qmltypes index 4d0f3a8c4e9cc647a4497713f3892237109e6c30..cc04aafd8d582715db5f40479021a1d4b9fadd43 100644 --- a/src/imports/qtcanvas3d/plugins.qmltypes +++ b/src/imports/qtcanvas3d/plugins.qmltypes @@ -4,7 +4,7 @@ import QtQuick.tooling 1.2 // It is used for QML tooling purposes only. // // This file was auto-generated by: -// 'qmlplugindump.exe -nonrelocatable QtCanvas3D 1.1' +// 'qmlplugindump -nonrelocatable QtCanvas3D 1.1' Module { dependencies: [] @@ -201,6 +201,8 @@ Module { name: "pixelSizeChanged" Parameter { name: "pixelSize"; type: "QSize" } } + Signal { name: "contextLost" } + Signal { name: "contextRestored" } Signal { name: "initializeGL" } Signal { name: "paintGL" } Signal { @@ -215,15 +217,7 @@ Module { Parameter { name: "size"; type: "QSize" } Parameter { name: "devicePixelRatio"; type: "float" } } - Method { name: "queueNextRender" } - Method { name: "queueResizeGL" } Method { name: "requestRender" } - Method { name: "emitNeedRender" } - Method { name: "handleBeforeSynchronizing" } - Method { - name: "handleRendererFpsChange" - Parameter { name: "fps"; type: "uint" } - } Method { name: "frameTimeMs"; type: "int" } Method { name: "frameSetupTimeMs"; revision: 1; type: "int" } Method { @@ -242,10 +236,15 @@ Module { name: "QtCanvas3D::CanvasAbstractObject" prototype: "QObject" Property { name: "name"; type: "string" } + Property { name: "invalidated"; type: "bool"; isReadonly: true } Signal { name: "nameChanged" Parameter { name: "name"; type: "string" } } + Signal { + name: "invalidatedChanged" + Parameter { name: "invalidated"; type: "bool" } + } } Component { name: "QtCanvas3D::CanvasActiveInfo" @@ -1465,11 +1464,6 @@ Module { name: "canvasChanged" Parameter { name: "canvas"; type: "Canvas"; isPointer: true } } - Method { name: "handleFullCommandQueue" } - Method { - name: "handleTextureIdResolved" - Parameter { name: "item"; type: "QQuickItem"; isPointer: true } - } Method { name: "getSupportedExtensions"; type: "QVariantList" } Method { name: "getExtension" diff --git a/src/imports/qtcanvas3d/program3d.cpp b/src/imports/qtcanvas3d/program3d.cpp index 0249f9f14795f07595f4f982ebaaf866f3d3758c..a4cd12ea25f5a6e9c315e39028108309ae2de07f 100644 --- a/src/imports/qtcanvas3d/program3d.cpp +++ b/src/imports/qtcanvas3d/program3d.cpp @@ -43,6 +43,7 @@ QT_CANVAS3D_BEGIN_NAMESPACE * \qmltype Canvas3DProgram * \since QtCanvas3D 1.0 * \inqmlmodule QtCanvas3D + * \inherits Canvas3DAbstractObject * \brief Contains a shader program. * * An uncreatable QML type that contains a compiled shader program. You can get it by calling @@ -54,9 +55,7 @@ CanvasProgram::CanvasProgram(CanvasGlCommandQueue *queue, QObject *parent) : m_programId(queue->createResourceId()), m_linked(false) { - Q_ASSERT(m_commandQueue); - - m_commandQueue->queueCommand(CanvasGlCommandQueue::glCreateProgram, m_programId); + queueCommand(CanvasGlCommandQueue::glCreateProgram, m_programId); } CanvasProgram::~CanvasProgram() @@ -74,8 +73,7 @@ void CanvasProgram::attach(CanvasShader *shader) if (m_programId) { if (m_attachedShaders.count(shader) == 0) { m_attachedShaders.append(shader); - m_commandQueue->queueCommand(CanvasGlCommandQueue::glAttachShader, - m_programId, shader->id()); + queueCommand(CanvasGlCommandQueue::glAttachShader, m_programId, shader->id()); } } } @@ -85,8 +83,7 @@ void CanvasProgram::detach(CanvasShader *shader) if (m_programId) { if (m_attachedShaders.count(shader) > 0) { m_attachedShaders.removeOne(shader); - m_commandQueue->queueCommand(CanvasGlCommandQueue::glDetachShader, - m_programId, shader->id()); + queueCommand(CanvasGlCommandQueue::glDetachShader, m_programId, shader->id()); } } } @@ -99,7 +96,7 @@ const QList<CanvasShader *> &CanvasProgram::attachedShaders() const void CanvasProgram::link() { if (m_programId) { - m_commandQueue->queueCommand(CanvasGlCommandQueue::glLinkProgram, m_programId); + queueCommand(CanvasGlCommandQueue::glLinkProgram, m_programId); m_linked = true; } } @@ -113,24 +110,22 @@ bool CanvasProgram::isLinked() void CanvasProgram::useProgram() { - if (m_programId) { - m_commandQueue->queueCommand(CanvasGlCommandQueue::glUseProgram, m_programId); - } + if (m_programId) + queueCommand(CanvasGlCommandQueue::glUseProgram, m_programId); } void CanvasProgram::bindAttributeLocation(int index, const QString &name) { if (m_programId) { - GlCommand &command = m_commandQueue->queueCommand(CanvasGlCommandQueue::glBindAttribLocation, - m_programId, GLint(index)); - command.data = new QByteArray(name.toLatin1()); + queueCommand(CanvasGlCommandQueue::glBindAttribLocation, new QByteArray(name.toLatin1()), + m_programId, GLint(index)); } } void CanvasProgram::del() { if (m_programId) { - m_commandQueue->queueCommand(CanvasGlCommandQueue::glDeleteProgram, m_programId); + queueCommand(CanvasGlCommandQueue::glDeleteProgram, m_programId); m_programId = 0; } m_attachedShaders.clear(); @@ -139,7 +134,7 @@ void CanvasProgram::del() void CanvasProgram::validateProgram() { if (m_programId) - m_commandQueue->queueCommand(CanvasGlCommandQueue::glValidateProgram, m_programId); + queueCommand(CanvasGlCommandQueue::glValidateProgram, m_programId); } GLint CanvasProgram::id() diff --git a/src/imports/qtcanvas3d/renderbuffer3d.cpp b/src/imports/qtcanvas3d/renderbuffer3d.cpp index 362777b95ae457da069a8a20cf8ba101ad9029f1..563ac4ef0c6c22f488882c9c36c722a6ea2e80dd 100644 --- a/src/imports/qtcanvas3d/renderbuffer3d.cpp +++ b/src/imports/qtcanvas3d/renderbuffer3d.cpp @@ -43,19 +43,24 @@ QT_CANVAS3D_BEGIN_NAMESPACE * \qmltype Canvas3DRenderBuffer * \since QtCanvas3D 1.0 * \inqmlmodule QtCanvas3D + * \inherits Canvas3DAbstractObject * \brief Contains an OpenGL renderbuffer. * * An uncreatable QML type that contains an OpenGL renderbuffer. You can get it by calling * the \l{Context3D::createRenderbuffer()}{Context3D.createRenderbuffer()} method. */ -CanvasRenderBuffer::CanvasRenderBuffer(CanvasGlCommandQueue *queue, QObject *parent) : +CanvasRenderBuffer::CanvasRenderBuffer(CanvasGlCommandQueue *queue, + bool initSecondaryId, QObject *parent) : CanvasAbstractObject(queue, parent), - m_renderbufferId(queue->createResourceId()) + m_renderbufferId(queue->createResourceId()), + m_secondaryId(0) { - Q_ASSERT(m_commandQueue); - - m_commandQueue->queueCommand(CanvasGlCommandQueue::glGenRenderbuffers, m_renderbufferId); + queueCommand(CanvasGlCommandQueue::glGenRenderbuffers, m_renderbufferId); + if (initSecondaryId) { + m_secondaryId = queue->createResourceId(); + queueCommand(CanvasGlCommandQueue::glGenRenderbuffers, m_secondaryId); + } } @@ -72,7 +77,11 @@ bool CanvasRenderBuffer::isAlive() void CanvasRenderBuffer::del() { if (m_renderbufferId) { - m_commandQueue->queueCommand(CanvasGlCommandQueue::glDeleteRenderbuffers, m_renderbufferId); + queueCommand(CanvasGlCommandQueue::glDeleteRenderbuffers, m_renderbufferId); + if (m_secondaryId) { + queueCommand(CanvasGlCommandQueue::glDeleteRenderbuffers, m_secondaryId); + m_secondaryId = 0; + } m_renderbufferId = 0; } } @@ -82,5 +91,10 @@ GLint CanvasRenderBuffer::id() return m_renderbufferId; } +GLint CanvasRenderBuffer::secondaryId() +{ + return m_secondaryId; +} + QT_CANVAS3D_END_NAMESPACE QT_END_NAMESPACE diff --git a/src/imports/qtcanvas3d/renderbuffer3d_p.h b/src/imports/qtcanvas3d/renderbuffer3d_p.h index 385ac6a167add140cffdb4c7855f6010375e33ae..5b634e65aac9bd2e6ceb399e77f4fe82e5b5c4cf 100644 --- a/src/imports/qtcanvas3d/renderbuffer3d_p.h +++ b/src/imports/qtcanvas3d/renderbuffer3d_p.h @@ -58,14 +58,17 @@ class CanvasRenderBuffer : public CanvasAbstractObject { Q_OBJECT public: - explicit CanvasRenderBuffer(CanvasGlCommandQueue *queue, QObject *parent = 0); + explicit CanvasRenderBuffer(CanvasGlCommandQueue *queue, bool initSecondaryId, + QObject *parent = 0); ~CanvasRenderBuffer(); bool isAlive(); void del(); GLint id(); + GLint secondaryId(); private: GLint m_renderbufferId; + GLint m_secondaryId; }; QT_CANVAS3D_END_NAMESPACE diff --git a/src/imports/qtcanvas3d/shader3d.cpp b/src/imports/qtcanvas3d/shader3d.cpp index 535e67afc7a93ddd491c6311e66f73b949424ad9..64ddde38203b9e9314b44e7eee7d0ebbcfa33ee6 100644 --- a/src/imports/qtcanvas3d/shader3d.cpp +++ b/src/imports/qtcanvas3d/shader3d.cpp @@ -43,6 +43,7 @@ QT_CANVAS3D_BEGIN_NAMESPACE * \qmltype Canvas3DShader * \since QtCanvas3D 1.0 * \inqmlmodule QtCanvas3D + * \inherits Canvas3DAbstractObject * \brief Contains a shader. * * An uncreatable QML type that contains a shader. You can get it by calling @@ -54,9 +55,7 @@ CanvasShader::CanvasShader(GLenum type, CanvasGlCommandQueue *queue, QObject *pa m_shaderId(queue->createResourceId()), m_sourceCode("") { - Q_ASSERT(m_commandQueue); - - m_commandQueue->queueCommand(CanvasGlCommandQueue::glCreateShader, GLint(type), m_shaderId); + queueCommand(CanvasGlCommandQueue::glCreateShader, GLint(type), m_shaderId); } CanvasShader::~CanvasShader() @@ -77,7 +76,7 @@ bool CanvasShader::isAlive() void CanvasShader::del() { if (m_shaderId) { - m_commandQueue->queueCommand(CanvasGlCommandQueue::glDeleteShader, m_shaderId); + queueCommand(CanvasGlCommandQueue::glDeleteShader, m_shaderId); m_shaderId = 0; } } @@ -95,9 +94,8 @@ void CanvasShader::setSourceCode(const QString &source) void CanvasShader::compileShader() { if (m_shaderId) { - GlCommand &command = m_commandQueue->queueCommand(CanvasGlCommandQueue::glCompileShader, - m_shaderId); - command.data = new QByteArray(m_sourceCode.toLatin1()); + queueCommand(CanvasGlCommandQueue::glCompileShader, new QByteArray(m_sourceCode.toLatin1()), + m_shaderId); } } diff --git a/src/imports/qtcanvas3d/texture3d.cpp b/src/imports/qtcanvas3d/texture3d.cpp index fafb5a7bc3dd9c97e22093451e6cbdd227e73749..6cc7aed7d2da2a81ed6597e311e81dc92e1afea3 100644 --- a/src/imports/qtcanvas3d/texture3d.cpp +++ b/src/imports/qtcanvas3d/texture3d.cpp @@ -44,6 +44,7 @@ QT_CANVAS3D_BEGIN_NAMESPACE * \qmltype Canvas3DTexture * \since QtCanvas3D 1.0 * \inqmlmodule QtCanvas3D + * \inherits Canvas3DAbstractObject * \brief Contains an OpenGL texture. * * An uncreatable QML type that contains an OpenGL texture. You can get it by calling @@ -58,12 +59,10 @@ CanvasTexture::CanvasTexture(CanvasGlCommandQueue *queue, CanvasContext *context m_context(context), m_quickItem(quickItem) { - Q_ASSERT(m_commandQueue); - if (m_quickItem) connect(m_quickItem, &QObject::destroyed, this, &CanvasTexture::handleItemDestroyed); else - m_commandQueue->queueCommand(CanvasGlCommandQueue::glGenTextures, m_textureId); + queueCommand(CanvasGlCommandQueue::glGenTextures, m_textureId); } CanvasTexture::~CanvasTexture() @@ -73,10 +72,8 @@ CanvasTexture::~CanvasTexture() void CanvasTexture::bind(CanvasContext::glEnums target) { - if (m_textureId) { - m_commandQueue->queueCommand(CanvasGlCommandQueue::glBindTexture, GLint(target), - m_textureId); - } + if (m_textureId) + queueCommand(CanvasGlCommandQueue::glBindTexture, GLint(target), m_textureId); } GLint CanvasTexture::textureId() const @@ -99,14 +96,13 @@ bool CanvasTexture::isAlive() const void CanvasTexture::del() { - if (m_textureId) { + if (!invalidated() && m_textureId) { if (m_quickItem) { m_context->quickItemToTextureMap().remove(m_quickItem); m_quickItem = 0; - m_commandQueue->queueCommand(CanvasGlCommandQueue::internalClearQuickItemAsTexture, - m_textureId); + queueCommand(CanvasGlCommandQueue::internalClearQuickItemAsTexture, m_textureId); } else { - m_commandQueue->queueCommand(CanvasGlCommandQueue::glDeleteTextures, m_textureId); + queueCommand(CanvasGlCommandQueue::glDeleteTextures, m_textureId); } } m_textureId = 0; diff --git a/src/imports/qtcanvas3d/uniformlocation.cpp b/src/imports/qtcanvas3d/uniformlocation.cpp index f76b0b2cb2e6774cbf37c7b20153d8d136a6f344..765b95a0bd011fe2059a306b2acd1bf66652745f 100644 --- a/src/imports/qtcanvas3d/uniformlocation.cpp +++ b/src/imports/qtcanvas3d/uniformlocation.cpp @@ -44,6 +44,7 @@ QT_CANVAS3D_BEGIN_NAMESPACE * \qmltype Canvas3DUniformLocation * \since QtCanvas3D 1.0 * \inqmlmodule QtCanvas3D + * \inherits Canvas3DAbstractObject * \brief Contains uniform location id. * * An uncreatable QML type that contains an uniform location id. You can get it by calling @@ -60,7 +61,7 @@ CanvasUniformLocation::CanvasUniformLocation(CanvasGlCommandQueue *queue, QObjec CanvasUniformLocation::~CanvasUniformLocation() { if (m_locationId) - m_commandQueue->queueCommand(CanvasGlCommandQueue::internalClearLocation, m_locationId); + queueCommand(CanvasGlCommandQueue::internalClearLocation, m_locationId); } GLint CanvasUniformLocation::id() diff --git a/tests/auto/qmltest/canvas3d/tst_render_dynamic.qml b/tests/auto/qmltest/canvas3d/tst_render_dynamic.qml index 2f4da23839d2bafbb3d2c8f57be486505bf08ca3..5ca74c47dcf18a32642ef795dc4d30c2de216343 100644 --- a/tests/auto/qmltest/canvas3d/tst_render_dynamic.qml +++ b/tests/auto/qmltest/canvas3d/tst_render_dynamic.qml @@ -34,16 +34,17 @@ ** ****************************************************************************/ -import QtQuick 2.2 -import QtCanvas3D 1.0 -import QtTest 1.0 +import QtQuick 2.6 +import QtCanvas3D 1.1 +import QtTest 1.1 +import QtQuick.Window 2.0 import "tst_render_simple.js" as Content Item { id: top - height: 300 - width: 300 + height: 200 + width: 200 property var canvas3d: null property var activeContent: Content @@ -51,31 +52,33 @@ Item { property bool renderOk: false property var canvasWindow: null property bool windowHidden: false + property var window1: null + property var window2: null - function createCanvas() { + function createCanvas(parentItem) { canvas3d = Qt.createQmlObject(" import QtQuick 2.2 - import QtCanvas3D 1.0 + import QtCanvas3D 1.1 Canvas3D { onInitializeGL: initOk = activeContent.initializeGL(canvas3d) onPaintGL: { renderOk = true activeContent.paintGL(canvas3d) } - }", top) - canvas3d.anchors.fill = top + }", parentItem) + canvas3d.anchors.fill = parentItem } function createCanvasWindow() { canvasWindow = Qt.createQmlObject(" import QtQuick 2.2 - import QtCanvas3D 1.0 + import QtCanvas3D 1.1 import QtQuick.Window 2.0 Window { property alias windowCanvas: theCanvas visible: true - width: 300 - height: 300 + width: 200 + height: 200 x: top.width y: top.height Canvas3D { @@ -93,22 +96,69 @@ Item { }", top) } + function createCanvasNoParent() { + var canvasComponent = Qt.createComponent("tst_render_dynamic_canvas_component.qml"); + if (canvasComponent.status === Component.Ready) { + canvas3d = canvasComponent.createObject(null) + } + } + + function createWindow() { + return Qt.createQmlObject(" + import QtQuick 2.2 + import QtCanvas3D 1.0 + import QtQuick.Window 2.0 + Window { + property alias windowTop: windowTop + visible: true + width: 200 + height: 200 + Item { + id: windowTop + anchors.fill: parent + } + }", top) + } + + function createWindow1() { + window1 = createWindow(); + window1.x = top.x + top.width + window1.y = top.y + } + function createWindow2() { + window2 = createWindow(); + window2.x = window1.x + window1.width + window2.y = window1.y + } + + function checkCanvasCorrect(windowTop) { + var image = theTestCase.grabImage(windowTop); + theTestCase.verify(image.pixel(5, 10) === Qt.rgba(0, 0, 1, 1)) + theTestCase.verify(image.pixel(5, 190) === Qt.rgba(0, 0, 1, 1)) + theTestCase.verify(image.pixel(95, 100) === Qt.rgba(0, 0, 1, 1)) + theTestCase.verify(image.pixel(195, 10) === Qt.rgba(0, 0, 0, 1)) + theTestCase.verify(image.pixel(195, 190) === Qt.rgba(0, 0, 0, 1)) + theTestCase.verify(image.pixel(105, 100) === Qt.rgba(0, 0, 0, 1)) + } + TestCase { + id: theTestCase name: "Canvas3D_render_dynamic" when: windowShown - function test_render_1_dynamic_creation() { + function test_render_01_dynamic_creation() { verify(canvas3d === null) verify(initOk === false) verify(renderOk === false) - createCanvas() + createCanvas(top) verify(canvas3d !== null) waitForRendering(canvas3d) tryCompare(top, "initOk", true) tryCompare(top, "renderOk", true) + checkCanvasCorrect(top) } - function test_render_2_dynamic_deletion() { + function test_render_02_dynamic_deletion() { verify(canvas3d !== null) verify(initOk === true) verify(renderOk === true) @@ -117,14 +167,15 @@ Item { verify(canvas3d === null) } - function test_render_3_dynamic_recreation() { + function test_render_03_dynamic_recreation() { initOk = false renderOk = false - createCanvas() + createCanvas(top) tryCompare(top, "initOk", true) tryCompare(top, "renderOk", true) waitForRendering(canvas3d) verify(canvas3d !== null) + checkCanvasCorrect(top) canvas3d.destroy() waitForRendering(canvas3d) @@ -132,50 +183,434 @@ Item { initOk = false renderOk = false - createCanvas() + createCanvas(top) waitForRendering(canvas3d) verify(canvas3d !== null) tryCompare(top, "initOk", true) tryCompare(top, "renderOk", true) + checkCanvasCorrect(top) canvas3d.destroy() waitForRendering(canvas3d) verify(canvas3d === null) } - function test_render_4_dynamic_window_creation() { + function test_render_04_dynamic_window_creation() { initOk = false renderOk = false verify(canvasWindow === null) createCanvasWindow() verify(canvasWindow !== null) + waitForRendering(canvasWindow.windowCanvas) tryCompare(top, "initOk", true) tryCompare(top, "renderOk", true) tryCompare(top, "windowHidden", false) + checkCanvasCorrect(canvasWindow.windowCanvas) canvasWindow.destroy() tryCompare(top, "canvasWindow", null) } - function test_render_5_dynamic_window_hide_and_reshow() { + function test_render_05_dynamic_window_hide_and_reshow() { initOk = false renderOk = false verify(canvasWindow === null) verify(windowHidden === false) createCanvasWindow() verify(canvasWindow !== null) + waitForRendering(canvasWindow.windowCanvas) tryCompare(top, "initOk", true) tryCompare(top, "renderOk", true) tryCompare(top, "windowHidden", false) - waitForRendering(canvasWindow.windowCanvas) + checkCanvasCorrect(canvasWindow.windowCanvas) canvasWindow.hide() + wait(1000) // Short wait to allow events to be processed tryCompare(top, "windowHidden", true) renderOk = false canvasWindow.show() waitForRendering(canvasWindow.windowCanvas) tryCompare(top, "renderOk", true) tryCompare(top, "windowHidden", false) + checkCanvasCorrect(canvasWindow.windowCanvas) canvasWindow.destroy() tryCompare(top, "canvasWindow", null) } + + function test_render_06_dynamic_no_parent() { + verify(canvas3d === null) + createCanvasNoParent() + verify(canvas3d !== null) + verify(canvas3d.parent === null) + canvas3d.destroy() + tryCompare(top, "canvas3d", null) + } + + function test_render_07_dynamic_from_no_parent_to_parent() { + verify(canvas3d === null) + createCanvasNoParent() + verify(canvas3d !== null) + verify(canvas3d.parent === null) + canvas3d.parent = top + waitForRendering(canvas3d) + tryCompare(canvas3d, "initOk", true) + tryCompare(canvas3d, "renderOk", true) + checkCanvasCorrect(top) + canvas3d.destroy() + tryCompare(top, "canvas3d", null) + } + + function test_render_08_dynamic_from_no_parent_to_parent_and_back() { + verify(canvas3d === null) + createCanvasNoParent() + verify(canvas3d !== null) + verify(canvas3d.parent === null) + + // Canvas to window + canvas3d.parent = top + waitForRendering(canvas3d) + tryCompare(canvas3d, "initOk", true) + tryCompare(canvas3d, "renderOk", true) + checkCanvasCorrect(top) + + // Remove canvas from window + canvas3d.parent = null + waitForRendering(top) + + // Canvas back to same window - should not reinitialize + canvas3d.initOk = false + canvas3d.renderOk = false + canvas3d.parent = top + waitForRendering(top) + tryCompare(canvas3d, "renderOk", true) + tryCompare(canvas3d, "initOk", false) + tryCompare(canvas3d, "contextLostOk", false) + checkCanvasCorrect(top) + + // Remove canvas from window + canvas3d.parent = null + waitForRendering(top) + + // Destroy canvas + canvas3d.destroy() + tryCompare(top, "canvas3d", null) + + // Create canvas again + createCanvasNoParent() + verify(canvas3d !== null) + verify(canvas3d.parent === null) + + // Canvas to window + canvas3d.parent = top + waitForRendering(canvas3d) + tryCompare(canvas3d, "initOk", true) + tryCompare(canvas3d, "renderOk", true) + tryCompare(canvas3d, "contextLostOk", false) + checkCanvasCorrect(top) + + // Remove canvas from window + canvas3d.parent = null + waitForRendering(top) + + // Canvas back to same window - should not reinitialize + canvas3d.initOk = false + canvas3d.renderOk = false + canvas3d.parent = top + waitForRendering(top) + tryCompare(canvas3d, "renderOk", true) + tryCompare(canvas3d, "initOk", false) + tryCompare(canvas3d, "contextLostOk", false) + checkCanvasCorrect(top) + + // Destroy canvas without removing from window + canvas3d.destroy() + tryCompare(top, "canvas3d", null) + } + + function test_render_09_dynamic_switch_between_two_windows() { + verify(canvas3d === null) + verify(window1 === null) + verify(window2 === null) + createCanvasNoParent() + createWindow1() + createWindow2() + verify(canvas3d !== null) + verify(window1 !== null) + verify(window2 !== null) + verify(canvas3d.parent === null) + + // Canvas to first window + canvas3d.parent = window1.windowTop + waitForRendering(window1.windowTop) + tryCompare(canvas3d, "initOk", true) + tryCompare(canvas3d, "renderOk", true) + tryCompare(canvas3d, "contextLostOk", false) + tryCompare(canvas3d, "contextRestoredOk", false) + checkCanvasCorrect(window1.windowTop) + + // Canvas to second window + canvas3d.initOk = false + canvas3d.renderOk = false + canvas3d.parent = window2.windowTop + waitForRendering(window2.windowTop) + tryCompare(canvas3d, "initOk", true) + tryCompare(canvas3d, "renderOk", true) + tryCompare(canvas3d, "contextLostOk", true) + tryCompare(canvas3d, "contextRestoredOk", true) + checkCanvasCorrect(window2.windowTop) + + // Canvas back to first window + canvas3d.initOk = false + canvas3d.renderOk = false + canvas3d.contextLostOk = false + canvas3d.contextRestoredOk = false + canvas3d.parent = window1.windowTop + waitForRendering(window1.windowTop) + tryCompare(canvas3d, "initOk", true) + tryCompare(canvas3d, "renderOk", true) + tryCompare(canvas3d, "contextLostOk", true) + tryCompare(canvas3d, "contextRestoredOk", true) + checkCanvasCorrect(window1.windowTop) + + // Canvas to no window, destroy canvas last + canvas3d.parent = null + waitForRendering(window1.windowTop) + window1.destroy() + window2.destroy() + canvas3d.destroy() + tryCompare(top, "canvas3d", null) + tryCompare(top, "window1", null) + tryCompare(top, "window2", null) + } + + function test_render_10_dynamic_canvas_to_window_which_is_destroyed_1() { + verify(canvas3d === null) + verify(window1 === null) + verify(window2 === null) + + // Canvas is created as child of the window + initOk = false + renderOk = false + createWindow1() + createCanvas(window1.windowTop) + verify(canvas3d !== null) + verify(window1 !== null) + verify(canvas3d.parent === window1.windowTop) + waitForRendering(window1.windowTop) + tryCompare(top, "initOk", true) + tryCompare(top, "renderOk", true) + checkCanvasCorrect(window1.windowTop) + + // Destroy window with canvas still on it - canvas should get destroyed also + window1.destroy() + tryCompare(top, "window1", null) + tryCompare(top, "canvas3d", null); + } + + function test_render_11_dynamic_canvas_to_window_which_is_destroyed_2() { + verify(canvas3d === null) + verify(window1 === null) + + // Create window, this time without canvas as child + createWindow1() + createCanvasNoParent() + verify(canvas3d !== null) + verify(window1 !== null) + verify(canvas3d.parent === null) + + // Set canvas as child of window + canvas3d.parent = window1.windowTop + waitForRendering(window1.windowTop) + tryCompare(canvas3d, "initOk", true) + tryCompare(canvas3d, "renderOk", true) + checkCanvasCorrect(window1.windowTop) + + // Destroy window with canvas as only visual child - canvas should not get autodestroyed + window1.destroy() + tryCompare(top, "window1", null) + verify(canvas3d !== null); + canvas3d.destroy(); + tryCompare(top, "canvas3d", null); + } + + function test_render_12_dynamic_canvas_to_window_which_is_destroyed_3() { + verify(canvas3d === null) + verify(window1 === null) + + // Create first window, no child + createWindow1() + createCanvasNoParent() + verify(canvas3d !== null) + verify(window1 !== null) + verify(canvas3d.parent === null) + + // Set canvas as child of window + canvas3d.parent = window1.windowTop + waitForRendering(window1.windowTop) + tryCompare(canvas3d, "initOk", true) + tryCompare(canvas3d, "renderOk", true) + checkCanvasCorrect(window1.windowTop) + + // Remove canvas from window + canvas3d.parent = null + waitForRendering(window1.windowTop) + tryCompare(canvas3d, "contextLostOk", false) + tryCompare(canvas3d, "contextRestoredOk", false) + + // Destroy window with canvas removed + window1.destroy() + tryCompare(top, "window1", null) + verify(canvas3d !== null); + tryCompare(canvas3d, "contextLostOk", true) + tryCompare(canvas3d, "contextRestoredOk", false) + var errorCheck = canvas3d.checkContextLostError() + verify(errorCheck === true) + canvas3d.destroy(); + tryCompare(top, "canvas3d", null); + } + + function test_render_13_dynamic_canvas_to_window_which_is_destroyed_4() { + verify(canvas3d === null) + verify(window1 === null) + verify(window2 === null) + + // Create two windows, no child + createWindow1() + createWindow2() + createCanvasNoParent() + verify(canvas3d !== null) + verify(window1 !== null) + verify(window2 !== null) + verify(canvas3d.parent === null) + + // Set canvas as child of first window + canvas3d.parent = window1.windowTop + waitForRendering(window1.windowTop) + tryCompare(canvas3d, "initOk", true) + tryCompare(canvas3d, "renderOk", true) + tryCompare(canvas3d, "contextLostOk", false) + tryCompare(canvas3d, "contextRestoredOk", false) + checkCanvasCorrect(window1.windowTop) + + // Remove canvas from first window + canvas3d.parent = null + waitForRendering(window1.windowTop) + + // Set canvas as child of second window - should reinitialize + canvas3d.initOk = false + canvas3d.renderOk = false + canvas3d.parent = window2.windowTop + waitForRendering(window2.windowTop) + tryCompare(canvas3d, "initOk", true) + tryCompare(canvas3d, "renderOk", true) + tryCompare(canvas3d, "contextLostOk", true) + tryCompare(canvas3d, "contextRestoredOk", true) + checkCanvasCorrect(window2.windowTop) + + // Destroy first window - should not affect canvas + canvas3d.initOk = false + canvas3d.renderOk = false + canvas3d.contextLostOk = false + canvas3d.contextRestoredOk = false + window1.destroy() + tryCompare(top, "window1", null) + waitForRendering(window2.windowTop) + verify(canvas3d !== null); + tryCompare(canvas3d, "renderOk", true) + tryCompare(canvas3d, "initOk", false) + tryCompare(canvas3d, "contextLostOk", false) + tryCompare(canvas3d, "contextRestoredOk", false) + checkCanvasCorrect(window2.windowTop) + + // Destroy second window - canvas only visual child, so doesn't get destroyed with it + window2.destroy() + tryCompare(top, "window2", null) + tryCompare(canvas3d, "contextLostOk", true) + tryCompare(canvas3d, "contextRestoredOk", false) + verify(canvas3d !== null); + canvas3d.destroy(); + tryCompare(top, "canvas3d", null); + } + + function test_render_14_dynamic_change_window_without_render_inbetween() { + verify(canvas3d === null) + verify(window1 === null) + verify(window2 === null) + createCanvasNoParent() + createWindow1() + createWindow2() + verify(canvas3d !== null) + verify(window1 !== null) + verify(window2 !== null) + verify(canvas3d.parent === null) + + // Canvas to first window + canvas3d.parent = window1.windowTop + waitForRendering(window1.windowTop) + tryCompare(canvas3d, "initOk", true) + tryCompare(canvas3d, "renderOk", true) + tryCompare(canvas3d, "contextLostOk", false) + tryCompare(canvas3d, "contextRestoredOk", false) + checkCanvasCorrect(window1.windowTop) + + // Toggle parent rapidly - should not affect canvas + canvas3d.initOk = false + canvas3d.renderOk = false + canvas3d.contextLostOk = false + canvas3d.contextRestoredOk = false + canvas3d.parent = null + canvas3d.parent = window1.windowTop + canvas3d.parent = null + canvas3d.parent = window1.windowTop + canvas3d.parent = null + canvas3d.parent = window1.windowTop + canvas3d.parent = null + canvas3d.parent = window1.windowTop + tryCompare(canvas3d, "initOk", false) + tryCompare(canvas3d, "renderOk", true) + tryCompare(canvas3d, "contextLostOk", false) + tryCompare(canvas3d, "contextRestoredOk", false) + checkCanvasCorrect(window1.windowTop) + + // Canvas to second window + canvas3d.initOk = false + canvas3d.renderOk = false + canvas3d.parent = window2.windowTop + waitForRendering(window2.windowTop) + tryCompare(canvas3d, "initOk", true) + tryCompare(canvas3d, "renderOk", true) + tryCompare(canvas3d, "contextLostOk", true) + tryCompare(canvas3d, "contextRestoredOk", true) + checkCanvasCorrect(window2.windowTop) + + // Change window rapidly - should cause context loss + canvas3d.initOk = false + canvas3d.renderOk = false + canvas3d.contextLostOk = false + canvas3d.contextRestoredOk = false + canvas3d.parent = window2.windowTop + canvas3d.parent = window1.windowTop + canvas3d.parent = null + canvas3d.parent = window1.windowTop + canvas3d.parent = window2.windowTop + canvas3d.parent = null + canvas3d.parent = window1.windowTop + canvas3d.parent = window2.windowTop + canvas3d.parent = null + canvas3d.parent = window2.windowTop + canvas3d.parent = window1.windowTop + canvas3d.parent = window2.windowTop + tryCompare(canvas3d, "initOk", true) + tryCompare(canvas3d, "renderOk", true) + tryCompare(canvas3d, "contextLostOk", true) + tryCompare(canvas3d, "contextRestoredOk", true) + checkCanvasCorrect(window2.windowTop) + + // Destroy windows and canvas + window1.destroy() + window2.destroy() + canvas3d.destroy() + tryCompare(top, "canvas3d", null) + tryCompare(top, "window1", null) + tryCompare(top, "window2", null) + } } } diff --git a/tests/auto/qmltest/canvas3d/tst_render_dynamic_canvas_component.qml b/tests/auto/qmltest/canvas3d/tst_render_dynamic_canvas_component.qml new file mode 100644 index 0000000000000000000000000000000000000000..723187344c07dfa21a8df69f01cc0c91ba35cc06 --- /dev/null +++ b/tests/auto/qmltest/canvas3d/tst_render_dynamic_canvas_component.qml @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtCanvas3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtCanvas3D 1.1 + +import "tst_render_simple.js" as Content + +Canvas3D { + id: canvas3d + anchors.fill: parent + + property var activeContent: Content + property bool initOk: false + property bool renderOk: false + property bool contextLostOk: false + property bool contextRestoredOk: false + + onInitializeGL: { + initOk = activeContent.initializeGL(canvas3d) + } + onPaintGL: { + renderOk = true + activeContent.paintGL(canvas3d) + } + onContextLost: { + contextLostOk = activeContent.checkContextLost(); + } + + onContextRestored: { + contextRestoredOk = activeContent.checkContextRestored(); + } + + function checkContextLostError() { + return activeContent.checkContextLostError(); + } +} diff --git a/tests/auto/qmltest/canvas3d/tst_render_simple.js b/tests/auto/qmltest/canvas3d/tst_render_simple.js index cd2c840f937fd31df6a37486fef3f111342c0c44..62e6186c7b1fa4f1826c452d1710bca2bce0b904 100644 --- a/tests/auto/qmltest/canvas3d/tst_render_simple.js +++ b/tests/auto/qmltest/canvas3d/tst_render_simple.js @@ -82,7 +82,11 @@ function initializeGL(canvas) { function paintGL(canvas) { gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.TRIANGLES, 0, 3); - return (gl.getError() === 0); + var err = 0; + if (canvas.renderTarget === Canvas3D.RenderTargetOffscreenBuffer) + err = gl.getError(); + return (err === 0); + } function resizeGL(canvas) @@ -144,3 +148,44 @@ function compileShader(str, type) { return shader; } + +function checkInvalidations() { + if (shaderProgram.invalidated === false) + return false; + if (buffer.invalidated === false) + return false; + if (vertexShader.invalidated === false) + return false; + if (fragmentShader.invalidated === false) + return false; + return true; +} + +function checkContextLost() { + var lostStatus = gl.isContextLost(); + if (lostStatus === true) { + return checkInvalidations(); + } else { + return false; + } +} + +function checkContextRestored() { + var lostStatus = gl.isContextLost(); + if (lostStatus === false) { + // All old objects should still be invalid, initializeGL will validate them + return checkInvalidations(); + } else { + return false; + } +} + +function checkContextLostError() { + var err = gl.getError(); + if (err === gl.CONTEXT_LOST_WEBGL) { + err = gl.getError(); + if (err === gl.NO_ERROR) + return true; + } + return false; +} diff --git a/tests/manual/commandthroughput/qml/commandthroughput/main.qml b/tests/manual/commandthroughput/qml/commandthroughput/main.qml index a234c0b695642c3fd245ba9fee4e0deb8d497fbd..cda57816866d5548fe7115b7ad97035fbf4c3de3 100644 --- a/tests/manual/commandthroughput/qml/commandthroughput/main.qml +++ b/tests/manual/commandthroughput/qml/commandthroughput/main.qml @@ -58,7 +58,6 @@ Item { property double xRotAnim: 0 property double yRotAnim: 0 property double zRotAnim: 0 - property bool isRunning: true property int itemCount: 0 property int maxCount: 5000 property int frameTime: 0 @@ -87,19 +86,6 @@ Item { onDevicePixelRatioChanged: { GLCode.onCanvasResize(canvas3d); } - - Keys.onSpacePressed: { - canvas3d.isRunning = !canvas3d.isRunning - if (canvas3d.isRunning) { - objAnimationX.pause(); - objAnimationY.pause(); - objAnimationZ.pause(); - } else { - objAnimationX.resume(); - objAnimationY.resume(); - objAnimationZ.resume(); - } - } } Timer { @@ -125,7 +111,7 @@ Item { id: fpsLabel Layout.alignment: Qt.AlignRight Layout.fillWidth: false - Layout.preferredWidth : 180 + Layout.preferredWidth : 200 text: "Fps: " + canvas3d.fps + " GL:" + canvas3d.frameTime + " onPaintGL:" + canvas3d.frameSetupTime color: "#FFFFFF" } diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro index fb886f381a807e7f7e11c953915358d30773ac01..b856c2d69a13546aa598126569e8188e0cf04ed4 100644 --- a/tests/manual/manual.pro +++ b/tests/manual/manual.pro @@ -1,2 +1,3 @@ TEMPLATE = subdirs -SUBDIRS = commandthroughput +SUBDIRS = commandthroughput \ + multiwindowtest diff --git a/tests/manual/multiwindowtest/main.cpp b/tests/manual/multiwindowtest/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..99470cd6e15af671b60db9f1bf959afaaea51985 --- /dev/null +++ b/tests/manual/multiwindowtest/main.cpp @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtCanvas3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 <QtGui/QGuiApplication> +#include <QtQml/QQmlApplicationEngine> + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + + engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); + + return app.exec(); +} diff --git a/tests/manual/multiwindowtest/multiwindowtest.pro b/tests/manual/multiwindowtest/multiwindowtest.pro new file mode 100644 index 0000000000000000000000000000000000000000..ab052312b0a0e7da6e54d8b875dae029abaad535 --- /dev/null +++ b/tests/manual/multiwindowtest/multiwindowtest.pro @@ -0,0 +1,17 @@ +TEMPLATE = app + +QT += qml quick + +SOURCES += main.cpp + +RESOURCES += multiwindowtest.qrc + +OTHER_FILES += qml/multiwindowtest/* + +DISTFILES += \ + qml/multiwindowtest/framebuffer.js \ + qml/multiwindowtest/quickitemtexture.js \ + qml/multiwindowtest/qtlogo.png \ + qml/multiwindowtest/framebuffer.qml \ + qml/multiwindowtest/quickitemtexture.qml + diff --git a/tests/manual/multiwindowtest/multiwindowtest.qrc b/tests/manual/multiwindowtest/multiwindowtest.qrc new file mode 100644 index 0000000000000000000000000000000000000000..cb387e1903007246e31399b58a167dd81f59d75e --- /dev/null +++ b/tests/manual/multiwindowtest/multiwindowtest.qrc @@ -0,0 +1,12 @@ +<RCC> + <qresource prefix="/"> + <file alias="gl-matrix.js">../../../examples/canvas3d/canvas3d/3rdparty/gl-matrix.js</file> + <file alias="main.qml">qml/multiwindowtest/main.qml</file> + <file alias="framebuffer.js">qml/multiwindowtest/framebuffer.js</file> + <file alias="framebuffer.qml">qml/multiwindowtest/framebuffer.qml</file> + <file alias="qtlogo.png">qml/multiwindowtest/qtlogo.png</file> + <file alias="quickitemtexture.js">qml/multiwindowtest/quickitemtexture.js</file> + <file alias="quickitemtexture.qml">qml/multiwindowtest/quickitemtexture.qml</file> + <file alias="canvaswindow.qml">qml/multiwindowtest/canvaswindow.qml</file> + </qresource> +</RCC> diff --git a/tests/manual/multiwindowtest/qml/multiwindowtest/canvaswindow.qml b/tests/manual/multiwindowtest/qml/multiwindowtest/canvaswindow.qml new file mode 100644 index 0000000000000000000000000000000000000000..3ddca17950729e839258c0e627d23bcb7b5bf9fe --- /dev/null +++ b/tests/manual/multiwindowtest/qml/multiwindowtest/canvaswindow.qml @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtCanvas3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Window 2.2 + +Window { + id: canvasWindow + width: 400 + height: 600 + visible: true + + property alias canvasArea: canvasArea + property var managerObject: null + property var canvas: null + property var previousCanvas: null + + function setManager(manager) { + managerObject = manager; + } + + onClosing: { + managerObject.removeWindow(canvasWindow) + } + + ColumnLayout { + spacing: 1 + anchors.fill: parent + visible: true + + Item { + id: canvasArea + Layout.fillHeight: true + Layout.fillWidth: true + Layout.preferredHeight: 400 + + onChildrenChanged: { + if (children.length === 0) { + previousCanvas = canvas + canvas = null + } + } + } + + Button { + Layout.fillHeight: true + Layout.fillWidth: true + text: "New QuickTexture canvas" + onClicked: { + previousCanvas = canvas + if (canvas) + canvas.parent = null + canvas = managerObject.createItemTextureCanvas(canvasArea) + } + } + + Button { + Layout.fillHeight: true + Layout.fillWidth: true + text: "New framebuffer canvas" + onClicked: { + previousCanvas = canvas + if (canvas) + canvas.parent = null + canvas = managerObject.createFboCanvas(canvasArea) + } + } + + Button { + Layout.fillHeight: true + Layout.fillWidth: true + text: "Grab random canvas" + onClicked: { + previousCanvas = canvas + if (canvas) + canvas.parent = null + canvas = chooseRandomCanvas() + if (canvas) + canvas.parent = canvasArea + } + } + + Button { + Layout.fillHeight: true + Layout.fillWidth: true + text: "Grab previous canvas" + onClicked: { + var previous = previousCanvas + previousCanvas = canvas + if (canvas) + canvas.parent = null + canvas = previous + if (canvas) + canvas.parent = canvasArea + } + } + + Button { + Layout.fillHeight: true + Layout.fillWidth: true + text: "Delete canvas" + onClicked: { + managerObject.deleteCanvas(canvas) + } + } + + Button { + Layout.fillHeight: true + Layout.fillWidth: true + text: "Release canvas" + onClicked: { + if (canvas) + canvas.parent = null + } + } + } +} diff --git a/tests/manual/multiwindowtest/qml/multiwindowtest/framebuffer.js b/tests/manual/multiwindowtest/qml/multiwindowtest/framebuffer.js new file mode 100644 index 0000000000000000000000000000000000000000..644a2f2e6952c4b26ea1d9938afd425d8680415b --- /dev/null +++ b/tests/manual/multiwindowtest/qml/multiwindowtest/framebuffer.js @@ -0,0 +1,460 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtCanvas3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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$ +** +****************************************************************************/ + +Qt.include("gl-matrix.js") + +// +// Draws a cube that has the Qt logo as decal texture on each face in to a texture. +// That texture is used as the texture for drawing another cube on the screen. +// + +var gl; + +var rttFramebuffer; +var rttTexture; +var rttWidth = 512; +var rttHeight = 512; + +var cubeTexture = 0; + +var vertexPositionAttribute; +var textureCoordAttribute; +var vertexNormalAttribute; +var vertexColorAttribute; +var mvMatrix = mat4.create(); +var pMatrix = mat4.create(); +var nMatrix = mat4.create(); +var pMatrixUniform; +var mvMatrixUniform; +var nUniform; + +var canvas3d; +var isLogEnabled = false; + +function log(message) { + if (isLogEnabled) + console.log(message) +} + +function initializeGL(canvas, textureLoader) { + canvas3d = canvas + try { + // Get the OpenGL context object that represents the API we call + gl = canvas.getContext("canvas3d", {depth:true, antialias:true}); + + // Setup the OpenGL state + gl.enable(gl.DEPTH_TEST); + gl.enable(gl.CULL_FACE); + gl.cullFace(gl.BACK); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); + + // Initialize the shader program + initShaders(); + + // Initialize vertex and color buffers + initBuffers(); + + // Load the Qt logo as texture + var qtLogoImage = TextureImageFactory.newTexImage(); + qtLogoImage.imageLoaded.connect(function() { + cubeTexture = gl.createTexture(); + cubeTexture.name = "CubeTexture"; + gl.bindTexture(gl.TEXTURE_2D, cubeTexture); + gl.texImage2D(gl.TEXTURE_2D, // target + 0, // level + gl.RGBA, // internalformat + gl.RGBA, // format + gl.UNSIGNED_BYTE, // type + qtLogoImage); // pixels + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST); + gl.generateMipmap(gl.TEXTURE_2D); + }); + qtLogoImage.imageLoadingFailed.connect(function() { + console.log("Texture load FAILED, "+qtLogoImage.errorString); + }); + qtLogoImage.src = "qrc:/qtlogo.png"; + + // Create the framebuffer object + rttFramebuffer = gl.createFramebuffer(); + rttFramebuffer.name = "OffscreenRenderTarget"; + gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer); + + // Create the texture + rttTexture = gl.createTexture(); + rttTexture.name = "OffscreenRenderTargetTexture"; + gl.bindTexture(gl.TEXTURE_2D, rttTexture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST); + gl.texImage2D(gl.TEXTURE_2D, 0, + gl.RGBA, rttWidth, rttHeight, + 0, gl.RGBA, gl.UNSIGNED_BYTE, + null); + gl.generateMipmap(gl.TEXTURE_2D); + + // Bind the texture as color attachment, create and bind a depth buffer + gl.framebufferTexture2D(gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, rttTexture, 0); + var renderbuffer = gl.createRenderbuffer(); + gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer); + gl.renderbufferStorage(gl.RENDERBUFFER, + gl.DEPTH_COMPONENT16, + rttWidth, rttHeight); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.RENDERBUFFER, renderbuffer); + gl.bindTexture(gl.TEXTURE_2D, 0); + gl.bindRenderbuffer(gl.RENDERBUFFER, 0); + gl.bindFramebuffer(gl.FRAMEBUFFER, 0); + } catch(e) { + console.log("initializeGL FAILURE!"); + console.log(""+e); + console.log(""+e.message); + } +} + +function handleContextLost() { + // Null all used textures so we don't get invalid operation on first few paints + // after context lost while the textures re-resolve. + rttTexture = null; + cubeTexture = null; +} + +function degToRad(degrees) { + return degrees * Math.PI / 180; +} + +function paintGL(canvas, clear) { + // bind the FBO and setup viewport + gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer); + gl.viewport(0, 0, rttWidth, rttHeight); + + // Clear the cube buffer + gl.clearColor(0.95, 0.95, 0.95, 1.0); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + // Bind the loaded texture + gl.bindTexture(gl.TEXTURE_2D, cubeTexture); + + // Calculate and set matrix uniforms + mat4.perspective(pMatrix, degToRad(45), rttWidth / rttHeight, 0.1, 100.0); + gl.uniformMatrix4fv(pMatrixUniform, false, pMatrix); + + mat4.identity(mvMatrix); + mat4.translate(mvMatrix, mvMatrix, [0, 0, -5.0]); + mat4.rotate(mvMatrix, mvMatrix, degToRad(canvas.xRotSlider), [0, 1, 0]); + mat4.rotate(mvMatrix, mvMatrix, degToRad(canvas.yRotSlider), [1, 0, 0]); + mat4.rotate(mvMatrix, mvMatrix, degToRad(canvas.zRotSlider), [0, 0, 1]); + gl.uniformMatrix4fv(mvMatrixUniform, false, mvMatrix); + + mat4.invert(nMatrix, mvMatrix); + mat4.transpose(nMatrix, nMatrix); + gl.uniformMatrix4fv(nUniform, false, nMatrix); + + // Draw the cube to the FBO + gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0); + + // Bind the render-to-texture and generate mipmaps + gl.bindTexture(gl.TEXTURE_2D, rttTexture); + gl.generateMipmap(gl.TEXTURE_2D); + + // Bind default framebuffer and setup viewport accordingly + gl.bindFramebuffer(gl.FRAMEBUFFER, 0); + gl.viewport(0, 0, + canvas.width * canvas.devicePixelRatio, + canvas.height * canvas.devicePixelRatio); + + // Clear the canvas buffer + gl.clearColor(0.0, 0.0, 0.0, 0.0); + if (clear) + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + // Calculate and set matrix uniforms + mat4.perspective(pMatrix, degToRad(45), canvas.width / canvas.height, 0.1, 100.0); + gl.uniformMatrix4fv(pMatrixUniform, false, pMatrix); + + mat4.identity(mvMatrix); + mat4.translate(mvMatrix, mvMatrix, [(canvas.yRotAnim - 120.0) / 120.0, + (canvas.xRotAnim - 60.0) / 50.0, + -10.0]); + mat4.rotate(mvMatrix, mvMatrix, degToRad(canvas.xRotAnim), [0, 1, 0]); + gl.uniformMatrix4fv(mvMatrixUniform, false, mvMatrix); + + mat4.invert(nMatrix, mvMatrix); + mat4.transpose(nMatrix, nMatrix); + gl.uniformMatrix4fv(nUniform, false, nMatrix); + + // Draw the on-screen cube + gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0); +} + +function resizeGL(canvas) +{ + var pixelRatio = canvas.devicePixelRatio; + canvas.pixelSize = Qt.size(canvas.width * pixelRatio, + canvas.height * pixelRatio); +} + +function initBuffers() +{ + log(" cubeVertexPositionBuffer"); + var cubeVertexPositionBuffer = gl.createBuffer(); + cubeVertexPositionBuffer.name = "cubeVertexPositionBuffer"; + gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([// Front face + -1.0, -1.0, 1.0, + 1.0, -1.0, 1.0, + 1.0, 1.0, 1.0, + -1.0, 1.0, 1.0, + + // Back face + -1.0, -1.0, -1.0, + -1.0, 1.0, -1.0, + 1.0, 1.0, -1.0, + 1.0, -1.0, -1.0, + + // Top face + -1.0, 1.0, -1.0, + -1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, -1.0, + + // Bottom face + -1.0, -1.0, -1.0, + 1.0, -1.0, -1.0, + 1.0, -1.0, 1.0, + -1.0, -1.0, 1.0, + + // Right face + 1.0, -1.0, -1.0, + 1.0, 1.0, -1.0, + 1.0, 1.0, 1.0, + 1.0, -1.0, 1.0, + + // Left face + -1.0, -1.0, -1.0, + -1.0, -1.0, 1.0, + -1.0, 1.0, 1.0, + -1.0, 1.0, -1.0 + ]), + gl.STATIC_DRAW); + + gl.enableVertexAttribArray(vertexPositionAttribute); + gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0); + + log(" cubeVertexIndexBuffer"); + var cubeVertexIndexBuffer = gl.createBuffer(); + cubeVertexIndexBuffer.name = "cubeVertexIndexBuffer"; + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, + new Uint16Array([ + 0, 1, 2, 0, 2, 3, // front + 4, 5, 6, 4, 6, 7, // back + 8, 9, 10, 8, 10, 11, // top + 12, 13, 14, 12, 14, 15, // bottom + 16, 17, 18, 16, 18, 19, // right + 20, 21, 22, 20, 22, 23 // left + ]), + gl.STATIC_DRAW); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer); + + log(" cubeVerticesTextureCoordBuffer"); + var cubeVerticesTextureCoordBuffer = gl.createBuffer(); + cubeVerticesTextureCoordBuffer.name = "cubeVerticesTextureCoordBuffer"; + gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesTextureCoordBuffer); + var textureCoordinates = [ + // Front + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0, + // Back + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0, + // Top + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0, + // Bottom + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0, + // Right + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0, + // Left + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0 + ]; + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), + gl.STATIC_DRAW); + gl.enableVertexAttribArray(textureCoordAttribute); + gl.vertexAttribPointer(textureCoordAttribute, 2, gl.FLOAT, false, 0, 0); + + var cubeVerticesNormalBuffer = gl.createBuffer(); + cubeVerticesNormalBuffer.name = "cubeVerticesNormalBuffer"; + gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesNormalBuffer); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ + // Front + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + + // Back + 0.0, 0.0, -1.0, + 0.0, 0.0, -1.0, + 0.0, 0.0, -1.0, + 0.0, 0.0, -1.0, + + // Top + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + + // Bottom + 0.0, -1.0, 0.0, + 0.0, -1.0, 0.0, + 0.0, -1.0, 0.0, + 0.0, -1.0, 0.0, + + // Right + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + + // Left + -1.0, 0.0, 0.0, + -1.0, 0.0, 0.0, + -1.0, 0.0, 0.0, + -1.0, 0.0, 0.0 + ]), gl.STATIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesNormalBuffer); + gl.vertexAttribPointer(vertexNormalAttribute, 3, gl.FLOAT, false, 0, 0); +} + +function initShaders() +{ + var vertexShader = getShader(gl, + "attribute highp vec3 aVertexNormal; \ + attribute highp vec3 aVertexPosition; \ + attribute highp vec2 aTextureCoord; \ + + uniform highp mat4 uNormalMatrix; \ + uniform mat4 uMVMatrix; \ + uniform mat4 uPMatrix; \ + + varying mediump vec4 vColor; \ + varying highp vec2 vTextureCoord; \ + varying highp vec3 vLighting; \ + + void main(void) { \ + gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); \ + vTextureCoord = aTextureCoord; \ + highp vec3 ambientLight = vec3(0.5, 0.5, 0.5); \ + highp vec3 directionalLightColor = vec3(0.75, 0.75, 0.75); \ + highp vec3 directionalVector = vec3(0.85, 0.8, 0.75); \ + highp vec4 transformedNormal = uNormalMatrix * vec4(aVertexNormal, 1.0); \ + highp float directional = max(dot(transformedNormal.xyz, directionalVector), 0.0); \ + vLighting = ambientLight + (directionalLightColor * directional); \ + }", gl.VERTEX_SHADER); + var fragmentShader = getShader(gl, + "varying highp vec2 vTextureCoord; \ + varying highp vec3 vLighting; \ + + uniform sampler2D uSampler; \ + + void main(void) { \ + mediump vec3 texelColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)).rgb; \ + gl_FragColor = vec4(texelColor * vLighting, 1.0); \ + }", gl.FRAGMENT_SHADER); + + var shaderProgram = gl.createProgram(); + gl.attachShader(shaderProgram, vertexShader); + gl.attachShader(shaderProgram, fragmentShader); + gl.linkProgram(shaderProgram); + + if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { + console.log("Could not initialize shaders"); + console.log(gl.getProgramInfoLog(shaderProgram)); + } + + gl.useProgram(shaderProgram); + + // look up where the vertex data needs to go. + vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition"); + gl.enableVertexAttribArray(vertexPositionAttribute); + textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord"); + gl.enableVertexAttribArray(textureCoordAttribute); + vertexNormalAttribute =gl.getAttribLocation(shaderProgram, "aVertexNormal"); + gl.enableVertexAttribArray(vertexNormalAttribute); + + pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix"); + mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix"); + nUniform = gl.getUniformLocation(shaderProgram, "uNormalMatrix"); + + var textureSamplerUniform = gl.getUniformLocation(shaderProgram, "uSampler") + gl.activeTexture(gl.TEXTURE0); + gl.uniform1i(textureSamplerUniform, 0); +} + +function getShader(gl, str, type) { + var shader = gl.createShader(type); + gl.shaderSource(shader, str); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + console.log("JS:Shader compile failed"); + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +} diff --git a/tests/manual/multiwindowtest/qml/multiwindowtest/framebuffer.qml b/tests/manual/multiwindowtest/qml/multiwindowtest/framebuffer.qml new file mode 100644 index 0000000000000000000000000000000000000000..3fe937826dd421db5068601d9cfe6bc30b3a7302 --- /dev/null +++ b/tests/manual/multiwindowtest/qml/multiwindowtest/framebuffer.qml @@ -0,0 +1,234 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtCanvas3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtCanvas3D 1.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 + +import "framebuffer.js" as GLCode + +Rectangle { + id: mainView + anchors.fill: parent + visible: true + color: "#f2f2f2" + + property alias canvas3d: canvas3d + property string canvasName: "" + property var previousParent: null + + onParentChanged: { + if (previousParent && previousParent.handleParentChange) + previousParent.handleParentChange() + previousParent = parent + } + + Canvas3D { + id: canvas3d + anchors.fill: parent + property double xRotSlider: 0 + property double yRotSlider: 0 + property double zRotSlider: 0 + property double xRotAnim: 0 + property double yRotAnim: 0 + property double zRotAnim: 0 + property bool isRunning: true + + // Emitted when one time initializations should happen + onInitializeGL: { + GLCode.initializeGL(canvas3d); + } + + // Emitted each time Canvas3D is ready for a new frame + onPaintGL: { + if (canvas3d.renderTarget === Canvas3D.RenderTargetOffscreenBuffer) + GLCode.paintGL(canvas3d, true); + else + GLCode.paintGL(canvas3d, false); + } + + onResizeGL: { + GLCode.resizeGL(canvas3d); + } + + onContextLost: { + console.log("Context lost on: ", mainView.canvasName) + GLCode.handleContextLost(); + } + + onContextRestored: { + console.log("Context restored on: ", mainView.canvasName) + } + + Keys.onSpacePressed: { + canvas3d.isRunning = !canvas3d.isRunning + if (canvas3d.isRunning) { + objAnimationX.pause(); + objAnimationY.pause(); + objAnimationZ.pause(); + } else { + objAnimationX.resume(); + objAnimationY.resume(); + objAnimationZ.resume(); + } + } + + SequentialAnimation { + id: objAnimationX + loops: Animation.Infinite + running: true + NumberAnimation { + target: canvas3d + property: "xRotAnim" + from: 0.0 + to: 120.0 + duration: 7000 + easing.type: Easing.InOutQuad + } + NumberAnimation { + target: canvas3d + property: "xRotAnim" + from: 120.0 + to: 0.0 + duration: 7000 + easing.type: Easing.InOutQuad + } + } + + SequentialAnimation { + id: objAnimationY + loops: Animation.Infinite + running: true + NumberAnimation { + target: canvas3d + property: "yRotAnim" + from: 0.0 + to: 240.0 + duration: 5000 + easing.type: Easing.InOutCubic + } + NumberAnimation { + target: canvas3d + property: "yRotAnim" + from: 240.0 + to: 0.0 + duration: 5000 + easing.type: Easing.InOutCubic + } + } + + SequentialAnimation { + id: objAnimationZ + loops: Animation.Infinite + running: true + NumberAnimation { + target: canvas3d + property: "zRotAnim" + from: -100.0 + to: 100.0 + duration: 3000 + easing.type: Easing.InOutSine + } + NumberAnimation { + target: canvas3d + property: "zRotAnim" + from: 100.0 + to: -100.0 + duration: 3000 + easing.type: Easing.InOutSine + } + } + } + + RowLayout { + id: controlLayout + spacing: 5 + x: 12 + y: parent.height - 100 + width: parent.width - (x * 2) + height: 100 + visible: true + + Label { + id: xRotLabel + Layout.alignment: Qt.AlignRight + Layout.fillWidth: false + text: "X-axis:" + } + + Slider { + id: xSlider + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + minimumValue: 0; + maximumValue: 360; + onValueChanged: canvas3d.xRotSlider = value; + } + + Label { + id: yRotLabel + Layout.alignment: Qt.AlignRight + Layout.fillWidth: false + text: "Y-axis:" + } + + Slider { + id: ySlider + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + minimumValue: 0; + maximumValue: 360; + onValueChanged: canvas3d.yRotSlider = value; + } + + Label { + id: zRotLabel + Layout.alignment: Qt.AlignRight + Layout.fillWidth: false + text: "Z-axis:" + } + + Slider { + id: zSlider + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + minimumValue: 0; + maximumValue: 360; + onValueChanged: canvas3d.zRotSlider = value; + } + } +} diff --git a/tests/manual/multiwindowtest/qml/multiwindowtest/main.qml b/tests/manual/multiwindowtest/qml/multiwindowtest/main.qml new file mode 100644 index 0000000000000000000000000000000000000000..969c14c4f3e18ab8edb117577c72fe8460e2d291 --- /dev/null +++ b/tests/manual/multiwindowtest/qml/multiwindowtest/main.qml @@ -0,0 +1,329 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtCanvas3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Window 2.2 +import QtCanvas3D 1.1 + +Window { + id: mainview + width: 400 + height: 600 + visible: true + title: "Windows:" + windowCount + " Canvases:" + canvasCount + + property var windowList: [] + property var canvasList: [] + property var windowComponent: null + property var fboCanvasComponent: null + property var itemTextureCanvasComponent: null + property int windowCount: 0 + property int canvasCount: 0 + property string renderTarget: "Offscreen" + + onClosing: { + for (var i = windowCount - 1; i >= 0; i--) + windowList[i].close() + } + + ColumnLayout { + id: controlLayout + spacing: 1 + anchors.fill: parent + visible: true + + Button { + Layout.fillHeight: true + Layout.fillWidth: true + text: "New window" + onClicked: { + createWindow() + } + } + + Button { + Layout.fillHeight: true + Layout.fillWidth: true + text: "New framebuffer canvas" + onClicked: { + createFboCanvas(null) + } + } + + Button { + Layout.fillHeight: true + Layout.fillWidth: true + text: "New QuickTexture canvas" + onClicked: { + createItemTextureCanvas(null) + } + } + + Button { + Layout.fillHeight: true + Layout.fillWidth: true + text: "Delete random canvas" + onClicked: { + deleteCanvas(chooseRandomCanvas()) + } + } + + Button { + Layout.fillHeight: true + Layout.fillWidth: true + text: "Delete first canvas" + onClicked: { + if (canvasCount > 0) + deleteCanvas(canvasList[0]) + } + } + + Button { + Layout.fillHeight: true + Layout.fillWidth: true + text: "Delete last canvas" + onClicked: { + if (canvasCount > 0) + deleteCanvas(canvasList[canvasCount - 1]) + } + } + + Button { + Layout.fillHeight: true + Layout.fillWidth: true + text: "RenderTarget for new canvases: " + renderTarget + onClicked: { + if (renderTarget === "Offscreen") { + renderTarget = "Background" + } else if (renderTarget === "Background") { + renderTarget = "Foreground" + } else { + renderTarget = "Offscreen" + } + } + } + + Button { + Layout.fillHeight: true + Layout.fillWidth: true + text: "Run test: Swapping two canvas in one window" + onClicked: { + if (singleWindowSwapTestTimer.running) { + if (singleWindowSwapTestCanvasFbo) + singleWindowSwapTestCanvasFbo.destroy() + if (singleWindowSwapTestCanvasQuickItem) + singleWindowSwapTestCanvasQuickItem.destroy() + if (singleWindowSwapTestWindow) { + removeWindow(singleWindowSwapTestWindow) + singleWindowSwapTestWindow.destroy() + } + singleWindowSwapTestTimer.stop() + } else { + singleWindowSwapTestWindow = createWindow() + singleWindowSwapTestCanvasFbo = createFboCanvas(null) + singleWindowSwapTestCanvasQuickItem = createItemTextureCanvas(null) + singleWindowSwapTestTimer.start() + } + } + } + + Button { + Layout.fillHeight: true + Layout.fillWidth: true + text: "Run test: Swapping one canvas in two windows" + onClicked: { + if (doubleWindowSwapTestTimer.running) { + if (doubleWindowSwapTestCanvas) + doubleWindowSwapTestCanvas.destroy() + if (doubleWindowSwapTestWindow1) { + removeWindow(doubleWindowSwapTestWindow1) + doubleWindowSwapTestWindow1.destroy() + } + if (doubleWindowSwapTestWindow2) { + removeWindow(doubleWindowSwapTestWindow2) + doubleWindowSwapTestWindow2.destroy() + } + doubleWindowSwapTestTimer.stop() + } else { + doubleWindowSwapTestWindow1 = createWindow() + doubleWindowSwapTestWindow2 = createWindow() + doubleWindowSwapTestCanvas = createFboCanvas(null) + + doubleWindowSwapTestTimer.start() + } + } + } + } + + property var singleWindowSwapTestWindow: null + property var singleWindowSwapTestCanvasFbo: null + property var singleWindowSwapTestCanvasQuickItem: null + + Timer { + id: singleWindowSwapTestTimer + interval: 200 + repeat: true + + property bool fboCanvasShown: false + property int counter: 0 + + onTriggered: { + console.log("Count:", counter++) + if (fboCanvasShown) { + fboCanvasShown = false + singleWindowSwapTestCanvasQuickItem.parent = null + singleWindowSwapTestCanvasFbo.parent = singleWindowSwapTestWindow.canvasArea + } else { + fboCanvasShown = true + singleWindowSwapTestCanvasFbo.parent = null + singleWindowSwapTestCanvasQuickItem.parent = singleWindowSwapTestWindow.canvasArea + } + } + } + + property var doubleWindowSwapTestWindow1: null + property var doubleWindowSwapTestWindow2: null + property var doubleWindowSwapTestCanvas: null + + Timer { + id: doubleWindowSwapTestTimer + interval: 200 + repeat: true + + property bool firstWindow: false + property int counter: 0 + + onTriggered: { + console.log("Count:", counter++) + if (firstWindow) { + firstWindow = false + doubleWindowSwapTestCanvas.parent = doubleWindowSwapTestWindow2.canvasArea + } else { + firstWindow = true + doubleWindowSwapTestCanvas.parent = doubleWindowSwapTestWindow1.canvasArea + } + } + } + + function createWindow() { + if (windowComponent === null) + windowComponent = Qt.createComponent("canvaswindow.qml") + var window = windowComponent.createObject(null) + window.setManager(mainview) + windowList[windowCount] = window + windowCount++ + window.x = windowCount * 20 + window.y = windowCount * 20 + return window + } + + function removeWindow(win) { + var found = false + for (var i = 0; i < windowCount; i++) { + if (windowList[i] === win) { + found = true + windowCount-- + } + if (found) { + if (i < windowCount) + windowList[i] = windowList[i + 1] + else + windowList[i] = null + } + } + } + + function deleteCanvas(canvas) { + var found = false + for (var i = 0; i < canvasCount; i++) { + if (canvasList[i] === canvas) { + found = true + canvasCount-- + console.log("Canvas deleted: ", canvasList[i].canvasName) + canvasList[i].destroy() + } + if (found) { + if (i < canvasCount) + canvasList[i] = canvasList[i + 1] + else + canvasList[i] = null + } + } + } + + function chooseRandomCanvas() { + if (canvasCount > 0) { + var index = Math.floor((Math.random() * canvasCount)) + console.log("Random canvas selected: ", canvasList[index].canvasName) + return canvasList[index] + } else { + return null + } + } + + function setupCanvas(canvas, canvasArea, canvasName) { + canvas.parent = canvasArea + canvasList[canvasCount] = canvas + canvasCount++ + canvas.canvasName = canvasName + console.log("Created ", canvas.canvasName) + if (renderTarget === "Offscreen") { + canvas.canvas3d.renderTarget = Canvas3D.RenderTargetOffscreenBuffer + } else if (renderTarget === "Background") { + canvas.color = "transparent" + canvas.canvas3d.renderTarget = Canvas3D.RenderTargetBackground + } else { + canvas.canvas3d.renderTarget = Canvas3D.RenderTargetForeground + } + return canvas + } + + function createFboCanvas(canvasArea) { + if (fboCanvasComponent === null) + fboCanvasComponent = Qt.createComponent("framebuffer.qml") + return setupCanvas(fboCanvasComponent.createObject(null), canvasArea, + "FBO canvas " + canvasCount) + } + + function createItemTextureCanvas(canvasArea) { + if (itemTextureCanvasComponent === null) + itemTextureCanvasComponent = Qt.createComponent("quickitemtexture.qml") + return setupCanvas(itemTextureCanvasComponent.createObject(null), canvasArea, + "QuickItem canvas " + canvasCount) + } +} diff --git a/tests/manual/multiwindowtest/qml/multiwindowtest/multiwindowtest.js b/tests/manual/multiwindowtest/qml/multiwindowtest/multiwindowtest.js new file mode 100644 index 0000000000000000000000000000000000000000..8433ec2dda5c21356409f46f060d51d2c47ac68f --- /dev/null +++ b/tests/manual/multiwindowtest/qml/multiwindowtest/multiwindowtest.js @@ -0,0 +1,386 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtCanvas3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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$ +** +****************************************************************************/ + +Qt.include("../../../examples/canvas3d/canvas3d/3rdparty/gl-matrix.js") + +// +// Draws a cube that has the Qt logo as decal texture on each face in to a texture. +// That texture is used as the texture for drawing another cube on the screen. +// + +var gl; + +var cubeTexture = 0; + +var vertexPositionAttribute; +var vertexNormalAttribute; +var vertexColorAttribute; +var mvMatrix = mat4.create(); +var pMatrix = mat4.create(); +var nMatrix = mat4.create(); +var pMatrixUniform; +var mvMatrixUniform; +var nUniform; + +var canvas3d; +var isLogEnabled = false; + +function log(message) { + if (isLogEnabled) + console.log(message) +} + +function initializeGL(canvas, textureLoader) { + canvas3d = canvas + try { + // Get the OpenGL context object that represents the API we call + gl = canvas.getContext("canvas3d", {depth:true, antialias:true}); + + // Setup the OpenGL state + gl.enable(gl.DEPTH_TEST); + gl.enable(gl.CULL_FACE); + gl.cullFace(gl.BACK); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); + + // Initialize the shader program + initShaders(); + + // Initialize vertex and color buffers + initBuffers(); + + } catch(e) { + console.log("initializeGL FAILURE!"); + console.log(""+e); + console.log(""+e.message); + } +} + +function degToRad(degrees) { + return degrees * Math.PI / 180; +} + +function paintGL(canvas) { + gl.viewport(0, 0, + canvas.width * canvas.devicePixelRatio, + canvas.height * canvas.devicePixelRatio); + + gl.clearColor(0.0, 0.0, 0.0, 1.0); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + var xStart = -6.0; + var yStart = -3.2; + var zStart = -15.0; + var xRange = 12.0; + var yRange = 7.0; + var zRange = 5.0; + + for (var count = 0; count < canvas3d.itemCount; count++) { + // Calculate and set matrix uniforms + mat4.perspective(pMatrix, degToRad(45), canvas.width / canvas.height, 0.1, 100.0); + gl.uniformMatrix4fv(pMatrixUniform, false, pMatrix); + + mat4.identity(mvMatrix); + var xFrac = ((count % 200) / 200); + var yFrac = count / canvas3d.maxCount; + var zFrac = ((count % 12) / 12); + mat4.translate(mvMatrix, mvMatrix, [xStart + (xFrac * xRange), + yStart + (yFrac * yRange), + zStart + (zFrac * zRange)]); + mat4.rotate(mvMatrix, mvMatrix, degToRad(canvas.xRotSlider), [0, 1, 0]); + mat4.rotate(mvMatrix, mvMatrix, degToRad(canvas.yRotSlider), [1, 0, 0]); + mat4.rotate(mvMatrix, mvMatrix, degToRad(canvas.zRotSlider), [0, 0, 1]); + mat4.scale(mvMatrix, mvMatrix, [0.15, 0.15, 0.15]) + gl.uniformMatrix4fv(mvMatrixUniform, false, mvMatrix); + + mat4.invert(nMatrix, mvMatrix); + mat4.transpose(nMatrix, nMatrix); + gl.uniformMatrix4fv(nUniform, false, nMatrix); + + // Draw the on-screen cube + gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0); + } +} + +function onCanvasResize(canvas) +{ + var pixelRatio = canvas.devicePixelRatio; + canvas.pixelSize = Qt.size(canvas.width * pixelRatio, + canvas.height * pixelRatio); +} + +function initBuffers() +{ + log(" cubeVertexPositionBuffer"); + var cubeVertexPositionBuffer = gl.createBuffer(); + cubeVertexPositionBuffer.name = "cubeVertexPositionBuffer"; + gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([// Front face + -1.0, -1.0, 1.0, + 1.0, -1.0, 1.0, + 1.0, 1.0, 1.0, + -1.0, 1.0, 1.0, + + // Back face + -1.0, -1.0, -1.0, + -1.0, 1.0, -1.0, + 1.0, 1.0, -1.0, + 1.0, -1.0, -1.0, + + // Top face + -1.0, 1.0, -1.0, + -1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, -1.0, + + // Bottom face + -1.0, -1.0, -1.0, + 1.0, -1.0, -1.0, + 1.0, -1.0, 1.0, + -1.0, -1.0, 1.0, + + // Right face + 1.0, -1.0, -1.0, + 1.0, 1.0, -1.0, + 1.0, 1.0, 1.0, + 1.0, -1.0, 1.0, + + // Left face + -1.0, -1.0, -1.0, + -1.0, -1.0, 1.0, + -1.0, 1.0, 1.0, + -1.0, 1.0, -1.0 + ]), + gl.STATIC_DRAW); + gl.enableVertexAttribArray(vertexPositionAttribute); + gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0); + + log(" cubeVertexIndexBuffer"); + var cubeVertexIndexBuffer = gl.createBuffer(); + cubeVertexIndexBuffer.name = "cubeVertexIndexBuffer"; + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, + new Uint16Array([ + 0, 1, 2, 0, 2, 3, // front + 4, 5, 6, 4, 6, 7, // back + 8, 9, 10, 8, 10, 11, // top + 12, 13, 14, 12, 14, 15, // bottom + 16, 17, 18, 16, 18, 19, // right + 20, 21, 22, 20, 22, 23 // left + ]), + gl.STATIC_DRAW); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer); + + var colors = [ + [0.0, 1.0, 1.0, 1.0], // Front face: white + [1.0, 0.0, 0.0, 1.0], // Back face: red + [0.0, 1.0, 0.0, 1.0], // Top face: green + [0.0, 0.0, 1.0, 1.0], // Bottom face: blue + [1.0, 1.0, 0.0, 1.0], // Right face: yellow + [1.0, 0.0, 1.0, 1.0] // Left face: purple + ]; + + var generatedColors = []; + for (var j = 0; j < 6; j++) { + var c = colors[j]; + + for (var i = 0; i < 4; i++) { + generatedColors = generatedColors.concat(c); + } + } + log(" cubeVertexColorBuffer"); + var cubeVertexColorBuffer = gl.createBuffer(); + cubeVertexColorBuffer.name = "cubeVertexColorBuffer"; + gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexColorBuffer); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(generatedColors), gl.STATIC_DRAW); + gl.enableVertexAttribArray(vertexColorAttribute); + gl.vertexAttribPointer(vertexColorAttribute, 4, gl.FLOAT, false, 0, 0); + + log(" cubeVerticesTextureCoordBuffer"); + var cubeVerticesTextureCoordBuffer = gl.createBuffer(); + cubeVerticesTextureCoordBuffer.name = "cubeVerticesTextureCoordBuffer"; + gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesTextureCoordBuffer); + var textureCoordinates = [ + // Front + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0, + // Back + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0, + // Top + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0, + // Bottom + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0, + // Right + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0, + // Left + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0 + ]; + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), + gl.STATIC_DRAW); + + var cubeVerticesNormalBuffer = gl.createBuffer(); + cubeVerticesNormalBuffer.name = "cubeVerticesNormalBuffer"; + gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesNormalBuffer); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ + // Front + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + + // Back + 0.0, 0.0, -1.0, + 0.0, 0.0, -1.0, + 0.0, 0.0, -1.0, + 0.0, 0.0, -1.0, + + // Top + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + + // Bottom + 0.0, -1.0, 0.0, + 0.0, -1.0, 0.0, + 0.0, -1.0, 0.0, + 0.0, -1.0, 0.0, + + // Right + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + + // Left + -1.0, 0.0, 0.0, + -1.0, 0.0, 0.0, + -1.0, 0.0, 0.0, + -1.0, 0.0, 0.0 + ]), gl.STATIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesNormalBuffer); + gl.vertexAttribPointer(vertexNormalAttribute, 3, gl.FLOAT, false, 0, 0); +} + +function initShaders() +{ + var vertexShader = getShader(gl, + "attribute highp vec3 aVertexNormal; \ + attribute highp vec3 aVertexPosition; \ + attribute mediump vec4 aVertexColor; \ + attribute highp vec2 aTextureCoord; \ + + uniform highp mat4 uNormalMatrix; \ + uniform mat4 uMVMatrix; \ + uniform mat4 uPMatrix; \ + + varying mediump vec4 vColor; \ + varying highp vec3 vLighting; \ + + void main(void) { \ + gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); \ + vColor = aVertexColor; \ + highp vec3 ambientLight = vec3(0.5, 0.5, 0.5); \ + highp vec3 directionalLightColor = vec3(0.15, 0.15, 0.15); \ + highp vec3 directionalVector = vec3(0.85, 0.8, 0.75); \ + highp vec4 transformedNormal = uNormalMatrix * vec4(aVertexNormal, 1.0); \ + highp float directional = max(dot(transformedNormal.xyz, directionalVector), 0.0); \ + vLighting = ambientLight + (directionalLightColor * directional); \ + }", gl.VERTEX_SHADER); + var fragmentShader = getShader(gl, + "varying mediump vec4 vColor; \ + varying highp vec3 vLighting; \ + + void main(void) { \ + gl_FragColor = vec4(vColor.rgb * vLighting, 1.0); \ + }", gl.FRAGMENT_SHADER); + + var shaderProgram = gl.createProgram(); + gl.attachShader(shaderProgram, vertexShader); + gl.attachShader(shaderProgram, fragmentShader); + gl.linkProgram(shaderProgram); + + if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { + console.log("Could not initialize shaders"); + console.log(gl.getProgramInfoLog(shaderProgram)); + } + + gl.useProgram(shaderProgram); + + // look up where the vertex data needs to go. + vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition"); + gl.enableVertexAttribArray(vertexPositionAttribute); + vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor"); + gl.enableVertexAttribArray(vertexColorAttribute); + vertexNormalAttribute =gl.getAttribLocation(shaderProgram, "aVertexNormal"); + gl.enableVertexAttribArray(vertexNormalAttribute); + + pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix"); + mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix"); + nUniform = gl.getUniformLocation(shaderProgram, "uNormalMatrix"); +} + +function getShader(gl, str, type) { + var shader = gl.createShader(type); + gl.shaderSource(shader, str); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + console.log("JS:Shader compile failed"); + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +} diff --git a/tests/manual/multiwindowtest/qml/multiwindowtest/qtlogo.png b/tests/manual/multiwindowtest/qml/multiwindowtest/qtlogo.png new file mode 100644 index 0000000000000000000000000000000000000000..f7276e2b9925b3a732bc0e28d1e1b36c7b6ed30b Binary files /dev/null and b/tests/manual/multiwindowtest/qml/multiwindowtest/qtlogo.png differ diff --git a/tests/manual/multiwindowtest/qml/multiwindowtest/quickitemtexture.js b/tests/manual/multiwindowtest/qml/multiwindowtest/quickitemtexture.js new file mode 100644 index 0000000000000000000000000000000000000000..308350454f1f3809d7a05cc31e3abd7bd603cb36 --- /dev/null +++ b/tests/manual/multiwindowtest/qml/multiwindowtest/quickitemtexture.js @@ -0,0 +1,368 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtCanvas3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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$ +** +****************************************************************************/ + +Qt.include("gl-matrix.js") + +// +// Draws a cube that has a Qt Quick item as decal texture on each face. +// A simple per vertex lighting equation is used to emulate light landing on the rotating cube. +// + +var gl; +var cubeTexture = 0; +var vertexPositionAttribute; +var textureCoordAttribute; +var vertexNormalAttribute; +var mvMatrix = mat4.create(); +var pMatrix = mat4.create(); +var nMatrix = mat4.create(); +var pMatrixUniform; +var mvMatrixUniform; +var nUniform; +var width = 0; +var height = 0; +var canvas3d; +var pixelSize; +var canvasTextureProvider = null; +var textureSourceItem = null; + +function initializeGL(canvas, textureSource) { + width = 0; + height = 0; + canvas3d = canvas; + textureSourceItem = textureSource; + + // Get the OpenGL context object that represents the API we call + gl = canvas.getContext("canvas3d", {depth:true, antialias:true}); + + // Setup the OpenGL state + gl.enable(gl.DEPTH_TEST); + gl.depthFunc(gl.LESS); + gl.enable(gl.CULL_FACE); + gl.cullFace(gl.BACK); + gl.clearDepth(1.0); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + // Use fully transparent clear color to allow other QML components to be seen through + // Canvas3D where not obscured by the rotating cube. + gl.clearColor(0.0, 0.0, 0.0, 0.0); + + // Set viewport + gl.viewport(0, 0, canvas.width, canvas.height); + + // Initialize the shader program + initShaders(); + + // Initialize vertex and color buffers + initBuffers(); + + // Create a texture from the Qt Quick item + canvasTextureProvider = gl.getExtension("QTCANVAS3D_texture_provider"); + cubeTexture = canvasTextureProvider.createTextureFromSource(textureSourceItem); +} + +function resizeGL(canvas) +{ + var pixelRatio = canvas.devicePixelRatio; + canvas.pixelSize = Qt.size(canvas.width * pixelRatio, + canvas.height * pixelRatio); +} + +function degToRad(degrees) { + return degrees * Math.PI / 180; +} + +function paintGL(canvas, clear) { + gl.bindTexture(gl.TEXTURE_2D, cubeTexture); + var pixelRatio = canvas.devicePixelRatio; + var currentWidth = canvas.width * pixelRatio; + var currentHeight = canvas.height * pixelRatio; + if (currentWidth !== width || currentHeight !== height ) { + width = currentWidth; + height = currentHeight; + gl.viewport(0, 0, width, height); + mat4.perspective(pMatrix, degToRad(45), width / height, 0.1, 500.0); + gl.uniformMatrix4fv(pMatrixUniform, false, pMatrix); + } + + if (clear) + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + mat4.identity(mvMatrix); + mat4.translate(mvMatrix, mvMatrix, [(canvas.yRotAnim - 120.0) / 120.0, + (canvas.xRotAnim - 60.0) / 50.0, + -7.0]); + mat4.rotate(mvMatrix, mvMatrix, degToRad(canvas.xRotAnim), [0, 1, 0]); + mat4.rotate(mvMatrix, mvMatrix, degToRad(canvas.yRotAnim), [1, 0, 0]); + mat4.rotate(mvMatrix, mvMatrix, degToRad(canvas.zRotAnim), [0, 0, 1]); + gl.uniformMatrix4fv(mvMatrixUniform, false, mvMatrix); + + mat4.invert(nMatrix, mvMatrix); + mat4.transpose(nMatrix, nMatrix); + gl.uniformMatrix4fv(nUniform, false, nMatrix); + + gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0); +} + +function initBuffers() +{ + var cubeVertexPositionBuffer = gl.createBuffer(); + cubeVertexPositionBuffer.name = "cubeVertexPositionBuffer"; + gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([// Front face + -1.0, -1.0, 1.0, + 1.0, -1.0, 1.0, + 1.0, 1.0, 1.0, + -1.0, 1.0, 1.0, + + // Back face + -1.0, -1.0, -1.0, + -1.0, 1.0, -1.0, + 1.0, 1.0, -1.0, + 1.0, -1.0, -1.0, + + // Top face + -1.0, 1.0, -1.0, + -1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, -1.0, + + // Bottom face + -1.0, -1.0, -1.0, + 1.0, -1.0, -1.0, + 1.0, -1.0, 1.0, + -1.0, -1.0, 1.0, + + // Right face + 1.0, -1.0, -1.0, + 1.0, 1.0, -1.0, + 1.0, 1.0, 1.0, + 1.0, -1.0, 1.0, + + // Left face + -1.0, -1.0, -1.0, + -1.0, -1.0, 1.0, + -1.0, 1.0, 1.0, + -1.0, 1.0, -1.0 + ]), + gl.STATIC_DRAW); + gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0); + + var cubeVertexIndexBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, + new Uint16Array([ + 0, 1, 2, 0, 2, 3, // front + 4, 5, 6, 4, 6, 7, // back + 8, 9, 10, 8, 10, 11, // top + 12, 13, 14, 12, 14, 15, // bottom + 16, 17, 18, 16, 18, 19, // right + 20, 21, 22, 20, 22, 23 // left + ]), + gl.STATIC_DRAW); + + var cubeVerticesTextureCoordBuffer = gl.createBuffer(); + cubeVerticesTextureCoordBuffer.name = "cubeVerticesTextureCoordBuffer"; + gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesTextureCoordBuffer); + var textureCoordinates = [ + // Front + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0, + // Back + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0, + // Top + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0, + // Bottom + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0, + // Right + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0, + // Left + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0 + ]; + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), + gl.STATIC_DRAW); + gl.vertexAttribPointer(textureCoordAttribute, 2, gl.FLOAT, false, 0, 0); + + var cubeVerticesNormalBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesNormalBuffer); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ + // Front + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + + // Back + 0.0, 0.0, -1.0, + 0.0, 0.0, -1.0, + 0.0, 0.0, -1.0, + 0.0, 0.0, -1.0, + + // Top + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + + // Bottom + 0.0, -1.0, 0.0, + 0.0, -1.0, 0.0, + 0.0, -1.0, 0.0, + 0.0, -1.0, 0.0, + + // Right + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + + // Left + -1.0, 0.0, 0.0, + -1.0, 0.0, 0.0, + -1.0, 0.0, 0.0, + -1.0, 0.0, 0.0 + ]), gl.STATIC_DRAW); + gl.vertexAttribPointer(vertexNormalAttribute, 3, gl.FLOAT, false, 0, 0); +} + +function initShaders() +{ + var vertexShader = getShader(gl, + "attribute highp vec3 aVertexNormal; \ + attribute highp vec3 aVertexPosition; \ + attribute highp vec2 aTextureCoord; \ + \ + uniform highp mat4 uNormalMatrix; \ + uniform mat4 uMVMatrix; \ + uniform mat4 uPMatrix; \ + \ + varying mediump vec4 vColor; \ + varying highp vec2 vTextureCoord; \ + varying highp vec3 vLighting; \ + \ + void main(void) { \ + gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); \ + vTextureCoord = aTextureCoord; \ + highp vec3 ambientLight = vec3(0.5, 0.5, 0.5); \ + highp vec3 directionalLightColor = vec3(0.75, 0.75, 0.75); \ + highp vec3 directionalVector = vec3(0.85, 0.8, 0.75); \ + highp vec4 transformedNormal = uNormalMatrix * vec4(aVertexNormal, 1.0); \ + highp float directional = max(dot(transformedNormal.xyz, directionalVector), 0.0); \ + vLighting = ambientLight + (directionalLightColor * directional); \ + }", gl.VERTEX_SHADER); + + var fragmentShader = getShader(gl, + "varying highp vec2 vTextureCoord; \ + varying highp vec3 vLighting; \ + \ + uniform sampler2D uSampler; \ + \ + void main(void) { \ + mediump vec3 texelColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)).rgb; \ + gl_FragColor = vec4(texelColor * vLighting, 1.0); \ + }", gl.FRAGMENT_SHADER); + + // Create the Program3D for shader + var shaderProgram = gl.createProgram(); + + // Attach the shader sources to the shader program + gl.attachShader(shaderProgram, vertexShader); + gl.attachShader(shaderProgram, fragmentShader); + + // Link the program + gl.linkProgram(shaderProgram); + + // Check the linking status + if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { + console.log("Could not initialize shaders"); + console.log(gl.getProgramInfoLog(shaderProgram)); + } + + // Take the shader program into use + gl.useProgram(shaderProgram); + + // Look up where the vertex data needs to go + vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition"); + gl.enableVertexAttribArray(vertexPositionAttribute); + textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord"); + gl.enableVertexAttribArray(textureCoordAttribute); + vertexNormalAttribute = gl.getAttribLocation(shaderProgram, "aVertexNormal"); + gl.enableVertexAttribArray(vertexNormalAttribute); + + // Get the uniform locations + pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix"); + mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix"); + nUniform = gl.getUniformLocation(shaderProgram, "uNormalMatrix"); + + // Setup texture sampler uniform + var textureSamplerUniform = gl.getUniformLocation(shaderProgram, "uSampler") + gl.activeTexture(gl.TEXTURE0); + gl.uniform1i(textureSamplerUniform, 0); + gl.bindTexture(gl.TEXTURE_2D, 0); +} + +function getShader(gl, str, type) { + var shader = gl.createShader(type); + gl.shaderSource(shader, str); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + console.log("JS:Shader compile failed"); + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +} diff --git a/tests/manual/multiwindowtest/qml/multiwindowtest/quickitemtexture.qml b/tests/manual/multiwindowtest/qml/multiwindowtest/quickitemtexture.qml new file mode 100644 index 0000000000000000000000000000000000000000..505fe5ca01f05d96e987411992bc93dccfd031b5 --- /dev/null +++ b/tests/manual/multiwindowtest/qml/multiwindowtest/quickitemtexture.qml @@ -0,0 +1,214 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtCanvas3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtCanvas3D 1.1 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.2 + +import "quickitemtexture.js" as GLCode + +Rectangle { + id: mainView + anchors.fill: parent + visible: true + color: "#f9f9f9" + + property alias canvas3d: canvas3d + property string canvasName: "" + property var previousParent: null + + onParentChanged: { + if (previousParent && previousParent.handleParentChange) + previousParent.handleParentChange() + previousParent = parent + } + + ColumnLayout { + Layout.fillWidth: true + x: 4 + y: 4 + Rectangle { + id: textureSource + color: "lightgreen" + width: 256 + height: 256 + border.color: "blue" + border.width: 4 + layer.enabled: true + layer.smooth: true + Label { + anchors.fill: parent + anchors.margins: 16 + text: "X Rot:" + (canvas3d.xRotAnim | 0) + "\n" + + "Y Rot:" + (canvas3d.yRotAnim | 0) + "\n" + + "Z Rot:" + (canvas3d.zRotAnim | 0) + "\n" + + "FPS:" + canvas3d.fps + color: "red" + font.pointSize: 30 + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + } + Button { + Layout.fillWidth: true + Layout.minimumWidth: 256 + text: textureSource.visible ? "Hide texture source" : "Show texture source" + onClicked: textureSource.visible = !textureSource.visible + } + Button { + Layout.fillWidth: true + Layout.minimumWidth: 256 + text: "Quit" + onClicked: Qt.quit() + } + } + + Canvas3D { + id: canvas3d + anchors.fill:parent + focus: true + property double xRotAnim: 0 + property double yRotAnim: 0 + property double zRotAnim: 0 + property bool isRunning: true + + // Emitted when one time initializations should happen + onInitializeGL: { + GLCode.initializeGL(canvas3d, textureSource); + } + + // Emitted each time Canvas3D is ready for a new frame + onPaintGL: { + if (canvas3d.renderTarget === Canvas3D.RenderTargetOffscreenBuffer) + GLCode.paintGL(canvas3d, true); + else + GLCode.paintGL(canvas3d, false); + } + + onResizeGL: { + GLCode.resizeGL(canvas3d); + } + + onContextLost: { + console.log("Context lost on: ", mainView.canvasName) + } + + onContextRestored: { + console.log("Context restored on: ", mainView.canvasName) + } + + Keys.onSpacePressed: { + canvas3d.isRunning = !canvas3d.isRunning + if (canvas3d.isRunning) { + objAnimationX.pause(); + objAnimationY.pause(); + objAnimationZ.pause(); + } else { + objAnimationX.resume(); + objAnimationY.resume(); + objAnimationZ.resume(); + } + } + + SequentialAnimation { + id: objAnimationX + loops: Animation.Infinite + running: true + NumberAnimation { + target: canvas3d + property: "xRotAnim" + from: 0.0 + to: 120.0 + duration: 7000 + easing.type: Easing.InOutQuad + } + NumberAnimation { + target: canvas3d + property: "xRotAnim" + from: 120.0 + to: 0.0 + duration: 7000 + easing.type: Easing.InOutQuad + } + } + + SequentialAnimation { + id: objAnimationY + loops: Animation.Infinite + running: true + NumberAnimation { + target: canvas3d + property: "yRotAnim" + from: 0.0 + to: 240.0 + duration: 5000 + easing.type: Easing.InOutCubic + } + NumberAnimation { + target: canvas3d + property: "yRotAnim" + from: 240.0 + to: 0.0 + duration: 5000 + easing.type: Easing.InOutCubic + } + } + + SequentialAnimation { + id: objAnimationZ + loops: Animation.Infinite + running: true + NumberAnimation { + target: canvas3d + property: "zRotAnim" + from: -100.0 + to: 100.0 + duration: 3000 + easing.type: Easing.InOutSine + } + NumberAnimation { + target: canvas3d + property: "zRotAnim" + from: 100.0 + to: -100.0 + duration: 3000 + easing.type: Easing.InOutSine + } + } + } +}