Improved QQuickShaderEffect::lookThroughShaderCode() performance.
authorKim Motoyoshi Kalland <kim.kalland@nokia.com>
Tue, 1 Nov 2011 17:11:38 +0000 (18:11 +0100)
committerQt by Nokia <qt-info@nokia.com>
Mon, 14 Nov 2011 13:19:34 +0000 (14:19 +0100)
Removed use of regexp. Skip comments and compiler directives in
the shader code. Don't look through default shader code.

Task-number: QTBUG-22423
Change-Id: Ie08cd8288ba7d7a33f1e3b0dc2ab5f2bedad04dd
Reviewed-by: Yoann Lopes <yoann.lopes@nokia.com>

src/declarative/items/qquickshadereffect.cpp

index 03247f9..ea7350d 100644 (file)
@@ -474,15 +474,25 @@ void QQuickShaderEffect::reset()
 
 void QQuickShaderEffect::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_source.vertexCode.isEmpty()) {
+        m_source.attributeNames.append(QByteArray(qt_position_attribute_name));
+        m_source.attributeNames.append(QByteArray(qt_texcoord_attribute_name));
+        m_source.respectsMatrix = true;
+    } else {
+        lookThroughShaderCode(m_source.vertexCode);
+    }
+    if (m_source.fragmentCode.isEmpty()) {
+        m_source.respectsOpacity = true;
+        QByteArray name("source");
+        m_source.uniformNames.insert(name);
+        SourceData d;
+        d.mapper = new QSignalMapper;
+        d.name = name;
+        d.sourceObject = 0;
+        m_sources.append(d);
+    } else {
+        lookThroughShaderCode(m_source.fragmentCode);
+    }
 
     if (!m_mesh && !m_source.attributeNames.contains(qt_position_attribute_name))
         qWarning("QQuickShaderEffect: Missing reference to \'%s\'.", qt_position_attribute_name);
@@ -501,38 +511,149 @@ void QQuickShaderEffect::updateProperties()
     connectPropertySignals();
 }
 
-void QQuickShaderEffect::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());
+namespace {
+
+    enum VariableQualifier {
+        AttributeQualifier,
+        UniformQualifier
+    };
 
-    int pos = -1;
+    inline bool qt_isalpha(char c)
+    {
+        char ch = c | 0x20;
+        return (ch >= 'a' && ch <= 'z') || c == '_';
+    }
 
-    QString wideCode = QString::fromLatin1(code.constData(), code.size());
+    inline bool qt_isalnum(char c)
+    {
+        return qt_isalpha(c) || (c >= '0' && c <= '9');
+    }
 
-    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
+    inline bool qt_isspace(char c)
+    {
+        return c == ' ' || (c >= 0x09 && c <= 0x0d);
+    }
 
-        if (decl == "attribute") {
-            m_source.attributeNames.append(name);
+    // Returns -1 if not found, returns index to first character after the name if found.
+    int qt_search_for_variable(const char *s, int length, int index, VariableQualifier &decl,
+                               int &typeIndex, int &typeLength,
+                               int &nameIndex, int &nameLength)
+    {
+        enum Identifier {
+            QualifierIdentifier, // Base state
+            PrecisionIdentifier,
+            TypeIdentifier,
+            NameIdentifier
+        };
+        Identifier expected = QualifierIdentifier;
+        bool compilerDirectiveExpected = index == 0;
+
+        while (index < length) {
+            // Skip whitespace.
+            while (qt_isspace(s[index])) {
+                compilerDirectiveExpected |= s[index] == '\n';
+                ++index;
+            }
+
+            if (qt_isalpha(s[index])) {
+                // Read identifier.
+                int identifierIndex = index;
+                ++index;
+                while (qt_isalnum(s[index]))
+                    ++index;
+                int identifierLength = index - identifierIndex;
+
+                switch (expected) {
+                case QualifierIdentifier:
+                    if (qstrncmp("attribute", s + identifierIndex, identifierLength) == 0) {
+                        decl = AttributeQualifier;
+                        expected = PrecisionIdentifier;
+                    } else if (qstrncmp("uniform", s + identifierIndex, identifierLength) == 0) {
+                        decl = UniformQualifier;
+                        expected = PrecisionIdentifier;
+                    }
+                    break;
+                case PrecisionIdentifier:
+                    if (qstrncmp("lowp", s + identifierIndex, identifierLength) == 0
+                            || qstrncmp("mediump", s + identifierIndex, identifierLength) == 0
+                            || qstrncmp("highp", s + identifierIndex, identifierLength) == 0)
+                    {
+                        expected = TypeIdentifier;
+                        break;
+                    }
+                    // Fall through.
+                case TypeIdentifier:
+                    typeIndex = identifierIndex;
+                    typeLength = identifierLength;
+                    expected = NameIdentifier;
+                    break;
+                case NameIdentifier:
+                    nameIndex = identifierIndex;
+                    nameLength = identifierLength;
+                    return index; // Attribute or uniform declaration found. Return result.
+                default:
+                    break;
+                }
+            } else if (s[index] == '#' && compilerDirectiveExpected) {
+                // Skip compiler directives.
+                ++index;
+                while (index < length && (s[index] != '\n' || s[index - 1] == '\\'))
+                    ++index;
+            } else if (s[index] == '/' && s[index + 1] == '/') {
+                // Skip comments.
+                index += 2;
+                while (index < length && s[index] != '\n')
+                    ++index;
+            } else if (s[index] == '/' && s[index + 1] == '*') {
+                // Skip comments.
+                index += 2;
+                while (index < length && (s[index] != '*' || s[index + 1] != '/'))
+                    ++index;
+                if (index < length)
+                    index += 2; // Skip star-slash.
+            } else {
+                expected = QualifierIdentifier;
+                ++index;
+            }
+            compilerDirectiveExpected = false;
+        }
+        return -1;
+    }
+}
+
+void QQuickShaderEffect::lookThroughShaderCode(const QByteArray &code)
+{
+    int index = 0;
+    int typeIndex, typeLength, nameIndex, nameLength;
+    const char *s = code.constData();
+    VariableQualifier decl;
+    while ((index = qt_search_for_variable(s, code.size(), index, decl, typeIndex, typeLength,
+                                           nameIndex, nameLength)) != -1)
+    {
+        if (decl == AttributeQualifier) {
+            m_source.attributeNames.append(QByteArray(s + nameIndex, nameLength));
         } else {
-            Q_ASSERT(decl == "uniform");
+            Q_ASSERT(decl == UniformQualifier);
+
+            const int matLen = sizeof("qt_Matrix") - 1;
+            const int opLen = sizeof("qt_Opacity") - 1;
+            const int mvpMatLen = sizeof("qt_ModelViewProjectionMatrix") - 1;
+            const int sampLen = sizeof("sampler2D") - 1;
 
-            if (name == "qt_Matrix") {
+            if (nameLength == matLen && qstrncmp("qt_Matrix", s + nameIndex, matLen) == 0) {
                 m_source.respectsMatrix = true;
-            } else if (name == "qt_ModelViewProjectionMatrix") {
+            } else if (nameLength == mvpMatLen && qstrncmp("qt_ModelViewProjectionMatrix",
+                                                           s + nameIndex, mvpMatLen) == 0)
+            {
                 // TODO: Remove after grace period.
                 qWarning("ShaderEffect: qt_ModelViewProjectionMatrix is deprecated. Use qt_Matrix instead.");
                 m_source.respectsMatrix = true;
-            } else if (name == "qt_Opacity") {
+            } else if (nameLength == opLen && qstrncmp("qt_Opacity", s + nameIndex, opLen) == 0) {
                 m_source.respectsOpacity = true;
             } else {
+                QByteArray name(s + nameIndex, nameLength);
                 m_source.uniformNames.insert(name);
-                if (type == "sampler2D") {
+                if (typeLength == sampLen && qstrncmp("sampler2D", s + typeIndex, sampLen) == 0) {
                     SourceData d;
                     d.mapper = new QSignalMapper;
                     d.name = name;