aboutsummaryrefslogtreecommitdiffstats
path: root/src/declarative/items/qsgshadereffectitem.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/declarative/items/qsgshadereffectitem.cpp')
-rw-r--r--src/declarative/items/qsgshadereffectitem.cpp449
1 files changed, 449 insertions, 0 deletions
diff --git a/src/declarative/items/qsgshadereffectitem.cpp b/src/declarative/items/qsgshadereffectitem.cpp
new file mode 100644
index 0000000000..286b67bacd
--- /dev/null
+++ b/src/declarative/items/qsgshadereffectitem.cpp
@@ -0,0 +1,449 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <private/qsgshadereffectitem_p.h>
+#include <private/qsgshadereffectnode_p.h>
+
+#include "qsgmaterial.h"
+#include "qsgitem_p.h"
+
+#include <private/qsgcontext_p.h>
+#include <private/qsgtextureprovider_p.h>
+#include "qsgcanvas.h"
+
+#include <QtCore/qsignalmapper.h>
+#include <QtOpenGL/qglframebufferobject.h>
+
+QT_BEGIN_NAMESPACE
+
+static const char qt_default_vertex_code[] =
+ "uniform highp mat4 qt_ModelViewProjectionMatrix; \n"
+ "attribute highp vec4 qt_Vertex; \n"
+ "attribute highp vec2 qt_MultiTexCoord0; \n"
+ "varying highp vec2 qt_TexCoord0; \n"
+ "void main() { \n"
+ " qt_TexCoord0 = qt_MultiTexCoord0; \n"
+ " gl_Position = qt_ModelViewProjectionMatrix * qt_Vertex; \n"
+ "}";
+
+static const char qt_default_fragment_code[] =
+ "varying highp vec2 qt_TexCoord0; \n"
+ "uniform sampler2D source; \n"
+ "uniform lowp float qt_Opacity; \n"
+ "void main() { \n"
+ " gl_FragColor = texture2D(source, qt_TexCoord0) * qt_Opacity; \n"
+ "}";
+
+static const char qt_position_attribute_name[] = "qt_Vertex";
+static const char qt_texcoord_attribute_name[] = "qt_MultiTexCoord0";
+
+const char *qtPositionAttributeName()
+{
+ return qt_position_attribute_name;
+}
+
+const char *qtTexCoordAttributeName()
+{
+ return qt_texcoord_attribute_name;
+}
+
+QSGShaderEffectItem::QSGShaderEffectItem(QSGItem *parent)
+ : QSGItem(parent)
+ , m_mesh(0)
+ , m_cullMode(NoCulling)
+ , m_blending(true)
+ , m_dirtyData(true)
+ , m_programDirty(true)
+ , m_dirtyMesh(true)
+ , m_dirtyGeometry(true)
+{
+ setFlag(QSGItem::ItemHasContents);
+}
+
+QSGShaderEffectItem::~QSGShaderEffectItem()
+{
+ reset();
+}
+
+void QSGShaderEffectItem::componentComplete()
+{
+ updateProperties();
+ QSGItem::componentComplete();
+}
+
+void QSGShaderEffectItem::setFragmentShader(const QByteArray &code)
+{
+ if (m_source.fragmentCode.constData() == code.constData())
+ return;
+ m_source.fragmentCode = code;
+ if (isComponentComplete()) {
+ reset();
+ updateProperties();
+ }
+ emit fragmentShaderChanged();
+}
+
+void QSGShaderEffectItem::setVertexShader(const QByteArray &code)
+{
+ if (m_source.vertexCode.constData() == code.constData())
+ return;
+ m_source.vertexCode = code;
+ if (isComponentComplete()) {
+ reset();
+ updateProperties();
+ }
+ emit vertexShaderChanged();
+}
+
+void QSGShaderEffectItem::setBlending(bool enable)
+{
+ if (blending() == enable)
+ return;
+
+ m_blending = enable;
+ update();
+
+ emit blendingChanged();
+}
+
+void QSGShaderEffectItem::setMesh(QSGShaderEffectMesh *mesh)
+{
+ if (mesh == m_mesh)
+ return;
+ if (m_mesh)
+ disconnect(m_mesh, SIGNAL(geometryChanged()), this, 0);
+ m_mesh = mesh;
+ if (m_mesh)
+ connect(m_mesh, SIGNAL(geometryChanged()), this, SLOT(updateGeometry()));
+ m_dirtyMesh = true;
+ update();
+ emit meshChanged();
+}
+
+void QSGShaderEffectItem::setCullMode(CullMode face)
+{
+ if (face == m_cullMode)
+ return;
+ m_cullMode = face;
+ update();
+ emit cullModeChanged();
+}
+
+void QSGShaderEffectItem::changeSource(int index)
+{
+ Q_ASSERT(index >= 0 && index < m_sources.size());
+ QVariant v = property(m_sources.at(index).name.constData());
+ setSource(v, index);
+}
+
+void QSGShaderEffectItem::updateData()
+{
+ m_dirtyData = true;
+ update();
+}
+
+void QSGShaderEffectItem::updateGeometry()
+{
+ m_dirtyGeometry = true;
+ update();
+}
+
+void QSGShaderEffectItem::setSource(const QVariant &var, int index)
+{
+ Q_ASSERT(index >= 0 && index < m_sources.size());
+
+ SourceData &source = m_sources[index];
+
+ source.item = 0;
+ if (var.isNull()) {
+ return;
+ } else if (!qVariantCanConvert<QObject *>(var)) {
+ qWarning("Could not assign source of type '%s' to property '%s'.", var.typeName(), source.name.constData());
+ return;
+ }
+
+ QObject *obj = qVariantValue<QObject *>(var);
+
+ QSGTextureProvider *int3rface = QSGTextureProvider::from(obj);
+ if (!int3rface) {
+ qWarning("Could not assign property '%s', did not implement QSGTextureProvider.", source.name.constData());
+ }
+
+ source.item = qobject_cast<QSGItem *>(obj);
+
+ // TODO: Find better solution.
+ // 'source.item' needs a canvas to get a scenegraph node.
+ // The easiest way to make sure it gets a canvas is to
+ // make it a part of the same item tree as 'this'.
+ if (source.item && source.item->parentItem() == 0) {
+ source.item->setParentItem(this);
+ source.item->setVisible(false);
+ }
+}
+
+void QSGShaderEffectItem::disconnectPropertySignals()
+{
+ disconnect(this, 0, this, SLOT(updateData()));
+ for (int i = 0; i < m_sources.size(); ++i) {
+ SourceData &source = m_sources[i];
+ disconnect(this, 0, source.mapper, 0);
+ disconnect(source.mapper, 0, this, 0);
+ }
+}
+
+void QSGShaderEffectItem::connectPropertySignals()
+{
+ QSet<QByteArray>::const_iterator it;
+ for (it = m_source.uniformNames.begin(); it != m_source.uniformNames.end(); ++it) {
+ int pi = metaObject()->indexOfProperty(it->constData());
+ if (pi >= 0) {
+ QMetaProperty mp = metaObject()->property(pi);
+ if (!mp.hasNotifySignal())
+ qWarning("QSGShaderEffectItem: property '%s' does not have notification method!", it->constData());
+ QByteArray signalName("2");
+ signalName.append(mp.notifySignal().signature());
+ connect(this, signalName, this, SLOT(updateData()));
+ } else {
+ qWarning("QSGShaderEffectItem: '%s' does not have a matching property!", it->constData());
+ }
+ }
+ for (int i = 0; i < m_sources.size(); ++i) {
+ SourceData &source = m_sources[i];
+ int pi = metaObject()->indexOfProperty(source.name.constData());
+ if (pi >= 0) {
+ QMetaProperty mp = metaObject()->property(pi);
+ QByteArray signalName("2");
+ signalName.append(mp.notifySignal().signature());
+ connect(this, signalName, source.mapper, SLOT(map()));
+ source.mapper->setMapping(this, i);
+ connect(source.mapper, SIGNAL(mapped(int)), this, SLOT(changeSource(int)));
+ } else {
+ qWarning("QSGShaderEffectItem: '%s' does not have a matching source!", source.name.constData());
+ }
+ }
+}
+
+void QSGShaderEffectItem::reset()
+{
+ disconnectPropertySignals();
+
+ m_source.attributeNames.clear();
+ m_source.uniformNames.clear();
+ m_source.respectsOpacity = false;
+ m_source.respectsMatrix = false;
+ m_source.className = metaObject()->className();
+
+ for (int i = 0; i < m_sources.size(); ++i) {
+ const SourceData &source = m_sources.at(i);
+ delete source.mapper;
+ if (source.item && source.item->parentItem() == this)
+ source.item->setParentItem(0);
+ }
+ m_sources.clear();
+
+ m_programDirty = true;
+ m_dirtyMesh = true;
+}
+
+void QSGShaderEffectItem::updateProperties()
+{
+ QByteArray vertexCode = m_source.vertexCode;
+ QByteArray fragmentCode = m_source.fragmentCode;
+ if (vertexCode.isEmpty())
+ vertexCode = qt_default_vertex_code;
+ if (fragmentCode.isEmpty())
+ fragmentCode = qt_default_fragment_code;
+
+ lookThroughShaderCode(vertexCode);
+ lookThroughShaderCode(fragmentCode);
+
+ if (!m_mesh && !m_source.attributeNames.contains(qt_position_attribute_name))
+ qWarning("QSGShaderEffectItem: Missing reference to \'%s\'.", qt_position_attribute_name);
+ if (!m_mesh && !m_source.attributeNames.contains(qt_texcoord_attribute_name))
+ qWarning("QSGShaderEffectItem: Missing reference to \'%s\'.", qt_texcoord_attribute_name);
+ if (!m_source.respectsMatrix)
+ qWarning("QSGShaderEffectItem: Missing reference to \'qt_ModelViewProjectionMatrix\'.");
+ if (!m_source.respectsOpacity)
+ qWarning("QSGShaderEffectItem: Missing reference to \'qt_Opacity\'.");
+
+ for (int i = 0; i < m_sources.size(); ++i) {
+ QVariant v = property(m_sources.at(i).name);
+ setSource(v, i);
+ }
+
+ connectPropertySignals();
+}
+
+void QSGShaderEffectItem::lookThroughShaderCode(const QByteArray &code)
+{
+ // Regexp for matching attributes and uniforms.
+ // In human readable form: attribute|uniform [lowp|mediump|highp] <type> <name>
+ static QRegExp re(QLatin1String("\\b(attribute|uniform)\\b\\s*\\b(?:lowp|mediump|highp)?\\b\\s*\\b(\\w+)\\b\\s*\\b(\\w+)"));
+ Q_ASSERT(re.isValid());
+
+ int pos = -1;
+
+ QString wideCode = QString::fromLatin1(code.constData(), code.size());
+
+ while ((pos = re.indexIn(wideCode, pos + 1)) != -1) {
+ QByteArray decl = re.cap(1).toLatin1(); // uniform or attribute
+ QByteArray type = re.cap(2).toLatin1(); // type
+ QByteArray name = re.cap(3).toLatin1(); // variable name
+
+ if (decl == "attribute") {
+ m_source.attributeNames.append(name);
+ } else {
+ Q_ASSERT(decl == "uniform");
+
+ if (name == "qt_ModelViewProjectionMatrix") {
+ m_source.respectsMatrix = true;
+ } else if (name == "qt_Opacity") {
+ m_source.respectsOpacity = true;
+ } else {
+ m_source.uniformNames.insert(name);
+ if (type == "sampler2D") {
+ SourceData d;
+ d.mapper = new QSignalMapper;
+ d.name = name;
+ d.item = 0;
+ m_sources.append(d);
+ }
+ }
+ }
+ }
+}
+
+void QSGShaderEffectItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
+{
+ m_dirtyGeometry = true;
+ QSGItem::geometryChanged(newGeometry, oldGeometry);
+}
+
+QSGNode *QSGShaderEffectItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
+{
+ QSGShaderEffectNode *node = static_cast<QSGShaderEffectNode *>(oldNode);
+
+ if (!node) {
+ node = new QSGShaderEffectNode;
+ node->setMaterial(&m_material);
+ m_programDirty = true;
+ m_dirtyData = true;
+ m_dirtyGeometry = true;
+ }
+
+ if (m_dirtyMesh) {
+ node->setGeometry(0);
+ m_dirtyMesh = false;
+ m_dirtyGeometry = true;
+ }
+
+ if (m_dirtyGeometry) {
+ node->setFlag(QSGNode::OwnsGeometry, false);
+ QSGGeometry *geometry = node->geometry();
+ QRectF rect(0, 0, width(), height());
+ QSGShaderEffectMesh *mesh = m_mesh ? m_mesh : &m_defaultMesh;
+
+ geometry = mesh->updateGeometry(geometry, m_source.attributeNames, rect);
+ if (!geometry) {
+ delete node;
+ return 0;
+ }
+
+ node->setGeometry(geometry);
+ node->setFlag(QSGNode::OwnsGeometry, true);
+
+ m_dirtyGeometry = false;
+ }
+
+ if (m_programDirty) {
+ QSGShaderEffectProgram s = m_source;
+ if (s.fragmentCode.isEmpty())
+ s.fragmentCode = qt_default_fragment_code;
+ if (s.vertexCode.isEmpty())
+ s.vertexCode = qt_default_vertex_code;
+
+ m_material.setProgramSource(s);
+ node->markDirty(QSGNode::DirtyMaterial);
+ m_programDirty = false;
+ }
+
+ // Update blending
+ if (bool(m_material.flags() & QSGMaterial::Blending) != m_blending) {
+ m_material.setFlag(QSGMaterial::Blending, m_blending);
+ node->markDirty(QSGNode::DirtyMaterial);
+ }
+
+ if (int(m_material.cullMode()) != int(m_cullMode)) {
+ m_material.setCullMode(QSGShaderEffectMaterial::CullMode(m_cullMode));
+ node->markDirty(QSGNode::DirtyMaterial);
+ }
+
+ if (m_dirtyData) {
+ QVector<QPair<QByteArray, QVariant> > values;
+ QVector<QPair<QByteArray, QPointer<QSGItem> > > textures;
+ const QVector<QPair<QByteArray, QPointer<QSGItem> > > &oldTextures = m_material.textureProviders();
+
+ for (QSet<QByteArray>::const_iterator it = m_source.uniformNames.begin();
+ it != m_source.uniformNames.end(); ++it) {
+ values.append(qMakePair(*it, property(*it)));
+ }
+ for (int i = 0; i < oldTextures.size(); ++i) {
+ QSGTextureProvider *oldSource = QSGTextureProvider::from(oldTextures.at(i).second);
+ if (oldSource && oldSource->textureChangedSignal())
+ disconnect(oldTextures.at(i).second, oldSource->textureChangedSignal(), node, SLOT(markDirtyTexture()));
+ }
+ for (int i = 0; i < m_sources.size(); ++i) {
+ const SourceData &source = m_sources.at(i);
+ textures.append(qMakePair(source.name, source.item));
+ QSGTextureProvider *t = QSGTextureProvider::from(source.item);
+ if (t && t->textureChangedSignal())
+ connect(source.item, t->textureChangedSignal(), node, SLOT(markDirtyTexture()));
+ }
+ m_material.setUniforms(values);
+ m_material.setTextureProviders(textures);
+ node->markDirty(QSGNode::DirtyMaterial);
+ m_dirtyData = false;
+ }
+
+ return node;
+}
+
+QT_END_NAMESPACE