commit e2357c2cb7174cdf3e5bd67d09fabd993ca862eb
Author: Rhys Weatherley <rhys.weatherley@nokia.com>
Date:   Fri Mar 12 12:52:42 2010 +1000

    Accelerate selected QMatrix4x4 operations with NEON intrinsics
    
    Task-number: QT-620
    Reviewed-by: TBD

diff --git a/src/gui/math3d/math3d.pri b/src/gui/math3d/math3d.pri
index e4dd53a..4047593 100644
--- a/src/gui/math3d/math3d.pri
+++ b/src/gui/math3d/math3d.pri
@@ -4,7 +4,8 @@ HEADERS += \
     math3d/qquaternion.h \
     math3d/qvector2d.h \
     math3d/qvector3d.h \
-    math3d/qvector4d.h
+    math3d/qvector4d.h \
+    math3d/qmath3dasm_neon.h
 
 SOURCES += \
     math3d/qgenericmatrix.cpp \
diff --git a/src/gui/math3d/qmath3dasm_neon.h b/src/gui/math3d/qmath3dasm_neon.h
new file mode 100644
index 0000000..de5ece9
--- /dev/null
+++ b/src/gui/math3d/qmath3dasm_neon.h
@@ -0,0 +1,220 @@
+/****************************************************************************
+**
+** 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 QtGui 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$
+**
+****************************************************************************/
+
+#if !defined(QT_MATH3D_DEFINE_MAT4X4_ASM)
+
+#if defined(__ARM_NEON__) && defined(__GNUC__) && !defined(QT_MATH3D_NEON)
+#include <arm_neon.h>
+#define QT_MATH3D_NEON 1
+#define QT_MATH3D_ASM_MAT4X4_MULTIPLY 1
+#define QT_MATH3D_ASM_VEC3_TRANSFORM 1
+#define QT_MATH3D_ASM_VEC4_TRANSFORM 1
+#endif
+
+#else // QT_MATH3D_DEFINE_MAT4X4_ASM
+
+inline QMatrix4x4& QMatrix4x4::operator*=(const QMatrix4x4& other)
+{
+    if (flagBits == Identity) {
+        *this = other;
+        return *this;
+    } else if (other.flagBits == Identity) {
+        return *this;
+    }
+
+    float32x4_t mat1col0 = vld1q_f32(mneon[0]);
+    float32x4_t mat1col1 = vld1q_f32(mneon[1]);
+    float32x4_t mat1col2 = vld1q_f32(mneon[2]);
+    float32x4_t mat1col3 = vld1q_f32(mneon[3]);
+
+    float32x4_t column;
+
+    column = vmulq_f32(mat1col0, vmovq_n_f32(other.mneon[0][0]));
+    column = vmlaq_f32(column, mat1col1, vmovq_n_f32(other.mneon[0][1]));
+    column = vmlaq_f32(column, mat1col2, vmovq_n_f32(other.mneon[0][2]));
+    column = vmlaq_f32(column, mat1col3, vmovq_n_f32(other.mneon[0][3]));
+    vst1q_f32(mneon[0], column);
+
+    column = vmulq_f32(mat1col0, vmovq_n_f32(other.mneon[1][0]));
+    column = vmlaq_f32(column, mat1col1, vmovq_n_f32(other.mneon[1][1]));
+    column = vmlaq_f32(column, mat1col2, vmovq_n_f32(other.mneon[1][2]));
+    column = vmlaq_f32(column, mat1col3, vmovq_n_f32(other.mneon[1][3]));
+    vst1q_f32(mneon[1], column);
+
+    column = vmulq_f32(mat1col0, vmovq_n_f32(other.mneon[2][0]));
+    column = vmlaq_f32(column, mat1col1, vmovq_n_f32(other.mneon[2][1]));
+    column = vmlaq_f32(column, mat1col2, vmovq_n_f32(other.mneon[2][2]));
+    column = vmlaq_f32(column, mat1col3, vmovq_n_f32(other.mneon[2][3]));
+    vst1q_f32(mneon[2], column);
+
+    column = vmulq_f32(mat1col0, vmovq_n_f32(other.mneon[3][0]));
+    column = vmlaq_f32(column, mat1col1, vmovq_n_f32(other.mneon[3][1]));
+    column = vmlaq_f32(column, mat1col2, vmovq_n_f32(other.mneon[3][2]));
+    column = vmlaq_f32(column, mat1col3, vmovq_n_f32(other.mneon[3][3]));
+    vst1q_f32(mneon[3], column);
+
+    flagBits = General;
+
+    return *this;
+}
+
+inline QMatrix4x4 operator*(const QMatrix4x4& m1, const QMatrix4x4& m2)
+{
+    if (m1.flagBits == QMatrix4x4::Identity)
+        return m2;
+    else if (m2.flagBits == QMatrix4x4::Identity)
+        return m1;
+
+    QMatrix4x4 m(1);
+
+    float32x4_t mat1col0 = vld1q_f32(m1.mneon[0]);
+    float32x4_t mat1col1 = vld1q_f32(m1.mneon[1]);
+    float32x4_t mat1col2 = vld1q_f32(m1.mneon[2]);
+    float32x4_t mat1col3 = vld1q_f32(m1.mneon[3]);
+
+    float32x4_t column;
+
+    column = vmulq_f32(mat1col0, vmovq_n_f32(m2.mneon[0][0]));
+    column = vmlaq_f32(column, mat1col1, vmovq_n_f32(m2.mneon[0][1]));
+    column = vmlaq_f32(column, mat1col2, vmovq_n_f32(m2.mneon[0][2]));
+    column = vmlaq_f32(column, mat1col3, vmovq_n_f32(m2.mneon[0][3]));
+    vst1q_f32(m.mneon[0], column);
+
+    column = vmulq_f32(mat1col0, vmovq_n_f32(m2.mneon[1][0]));
+    column = vmlaq_f32(column, mat1col1, vmovq_n_f32(m2.mneon[1][1]));
+    column = vmlaq_f32(column, mat1col2, vmovq_n_f32(m2.mneon[1][2]));
+    column = vmlaq_f32(column, mat1col3, vmovq_n_f32(m2.mneon[1][3]));
+    vst1q_f32(m.mneon[1], column);
+
+    column = vmulq_f32(mat1col0, vmovq_n_f32(m2.mneon[2][0]));
+    column = vmlaq_f32(column, mat1col1, vmovq_n_f32(m2.mneon[2][1]));
+    column = vmlaq_f32(column, mat1col2, vmovq_n_f32(m2.mneon[2][2]));
+    column = vmlaq_f32(column, mat1col3, vmovq_n_f32(m2.mneon[2][3]));
+    vst1q_f32(m.mneon[2], column);
+
+    column = vmulq_f32(mat1col0, vmovq_n_f32(m2.mneon[3][0]));
+    column = vmlaq_f32(column, mat1col1, vmovq_n_f32(m2.mneon[3][1]));
+    column = vmlaq_f32(column, mat1col2, vmovq_n_f32(m2.mneon[3][2]));
+    column = vmlaq_f32(column, mat1col3, vmovq_n_f32(m2.mneon[3][3]));
+    vst1q_f32(m.mneon[3], column);
+
+    return m;
+}
+
+inline QVector3D operator*(const QMatrix4x4& matrix, const QVector3D& vector)
+{
+    if (matrix.flagBits == QMatrix4x4::Identity) {
+        return vector;
+    } else if (matrix.flagBits == QMatrix4x4::Translation) {
+        return QVector3D(vector.x() + matrix.m[3][0],
+                         vector.y() + matrix.m[3][1],
+                         vector.z() + matrix.m[3][2]);
+    } else if (matrix.flagBits ==
+                    (QMatrix4x4::Translation | QMatrix4x4::Scale)) {
+        return QVector3D(vector.x() * matrix.m[0][0] + matrix.m[3][0],
+                         vector.y() * matrix.m[1][1] + matrix.m[3][1],
+                         vector.z() * matrix.m[2][2] + matrix.m[3][2]);
+    } else if (matrix.flagBits == QMatrix4x4::Scale) {
+        return QVector3D(vector.x() * matrix.m[0][0],
+                         vector.y() * matrix.m[1][1],
+                         vector.z() * matrix.m[2][2]);
+    } else {
+        float32x4_t result;
+        result = vmlaq_f32(vld1q_f32(matrix.mneon[3]),
+                           vld1q_f32(matrix.mneon[0]),
+                           vmovq_n_f32(vector.x()));
+        result = vmlaq_f32(result, vld1q_f32(matrix.mneon[1]),
+                           vmovq_n_f32(vector.y()));
+        result = vmlaq_f32(result, vld1q_f32(matrix.mneon[2]),
+                           vmovq_n_f32(vector.z()));
+        float32_t w = vgetq_lane_f32(result, 3);
+        if (w == 1.0f) {
+            return QVector3D(vgetq_lane_f32(result, 0),
+                             vgetq_lane_f32(result, 1),
+                             vgetq_lane_f32(result, 2));
+        } else {
+            result = vmulq_f32(result, vmovq_n_f32(1.0f / w));
+            return QVector3D(vgetq_lane_f32(result, 0),
+                             vgetq_lane_f32(result, 1),
+                             vgetq_lane_f32(result, 2));
+        }
+    }
+}
+
+inline QVector4D operator*(const QMatrix4x4& matrix, const QVector4D& vector)
+{
+    float32x4_t result;
+    result = vmulq_f32(vld1q_f32(matrix.mneon[0]), vmovq_n_f32(vector.x()));
+    result = vmlaq_f32(result, vld1q_f32(matrix.mneon[1]),
+                       vmovq_n_f32(vector.y()));
+    result = vmlaq_f32(result, vld1q_f32(matrix.mneon[2]),
+                       vmovq_n_f32(vector.z()));
+    result = vmlaq_f32(result, vld1q_f32(matrix.mneon[3]),
+                       vmovq_n_f32(vector.w()));
+    return QVector4D(vgetq_lane_f32(result, 0),
+                     vgetq_lane_f32(result, 1),
+                     vgetq_lane_f32(result, 2),
+                     vgetq_lane_f32(result, 3));
+}
+
+inline QVector3D QMatrix4x4::mapVector(const QVector3D& vector) const
+{
+    if (flagBits == Identity || flagBits == Translation) {
+        return vector;
+    } else if (flagBits == Scale || flagBits == (Translation | Scale)) {
+        return QVector3D(vector.x() * m[0][0],
+                         vector.y() * m[1][1],
+                         vector.z() * m[2][2]);
+    } else {
+        float32x4_t result;
+        result = vmulq_f32(vld1q_f32(mneon[0]),
+                           vmovq_n_f32(vector.x()));
+        result = vmlaq_f32(result, vld1q_f32(mneon[1]),
+                           vmovq_n_f32(vector.y()));
+        result = vmlaq_f32(result, vld1q_f32(mneon[2]),
+                           vmovq_n_f32(vector.z()));
+        return QVector3D(vgetq_lane_f32(result, 0),
+                         vgetq_lane_f32(result, 1),
+                         vgetq_lane_f32(result, 2));
+    }
+}
+
+#endif // QT_MATH3D_DEFINE_MAT4X4_ASM
diff --git a/src/gui/math3d/qmatrix4x4.h b/src/gui/math3d/qmatrix4x4.h
index 0671fa8..a5cbf39 100644
--- a/src/gui/math3d/qmatrix4x4.h
+++ b/src/gui/math3d/qmatrix4x4.h
@@ -48,6 +48,10 @@
 #include <QtGui/qgenericmatrix.h>
 #include <QtCore/qrect.h>
 
+#if defined(__ARM_NEON__)
+#include "qmath3dasm_neon.h"
+#endif
+
 QT_BEGIN_HEADER
 
 QT_BEGIN_NAMESPACE
@@ -187,7 +191,14 @@ public:
 #endif
 
 private:
+#if defined(QT_MATH3D_NEON)
+    union {
+        qreal m[4][4];
+        float32_t mneon[4][4];
+    };
+#else
     qreal m[4][4];          // Column-major order to match OpenGL.
+#endif
     int flagBits;           // Flag bits from the enum below.
 
     enum {
@@ -413,6 +424,8 @@ inline QMatrix4x4& QMatrix4x4::operator-=(const QMatrix4x4& other)
     return *this;
 }
 
+#if !defined(QT_MATH3D_ASM_MAT4X4_MULTIPLY)
+
 inline QMatrix4x4& QMatrix4x4::operator*=(const QMatrix4x4& other)
 {
     if (flagBits == Identity) {
@@ -426,6 +439,8 @@ inline QMatrix4x4& QMatrix4x4::operator*=(const QMatrix4x4& other)
     }
 }
 
+#endif // QT_MATH3D_ASM_MAT4X4_MULTIPLY
+
 inline QMatrix4x4& QMatrix4x4::operator*=(qreal factor)
 {
     m[0][0] *= factor;
@@ -532,6 +547,8 @@ inline QMatrix4x4 operator-(const QMatrix4x4& m1, const QMatrix4x4& m2)
     return m;
 }
 
+#if !defined(QT_MATH3D_ASM_MAT4X4_MULTIPLY)
+
 inline QMatrix4x4 operator*(const QMatrix4x4& m1, const QMatrix4x4& m2)
 {
     if (m1.flagBits == QMatrix4x4::Identity)
@@ -607,6 +624,8 @@ inline QMatrix4x4 operator*(const QMatrix4x4& m1, const QMatrix4x4& m2)
     return m;
 }
 
+#endif // QT_MATH3D_ASM_MAT4X4_MULTIPLY
+
 #ifndef QT_NO_VECTOR3D
 
 inline QVector3D operator*(const QVector3D& vector, const QMatrix4x4& matrix)
@@ -634,6 +653,8 @@ inline QVector3D operator*(const QVector3D& vector, const QMatrix4x4& matrix)
         return QVector3D(x / w, y / w, z / w);
 }
 
+#if !defined(QT_MATH3D_ASM_VEC3_TRANSFORM)
+
 inline QVector3D operator*(const QMatrix4x4& matrix, const QVector3D& vector)
 {
     qreal x, y, z, w;
@@ -676,6 +697,8 @@ inline QVector3D operator*(const QMatrix4x4& matrix, const QVector3D& vector)
     }
 }
 
+#endif // QT_MATH3D_ASM_VEC3_TRANSFORM
+
 #endif
 
 #ifndef QT_NO_VECTOR4D
@@ -702,6 +725,8 @@ inline QVector4D operator*(const QVector4D& vector, const QMatrix4x4& matrix)
     return QVector4D(x, y, z, w);
 }
 
+#if !defined(QT_MATH3D_ASM_VEC4_TRANSFORM)
+
 inline QVector4D operator*(const QMatrix4x4& matrix, const QVector4D& vector)
 {
     qreal x, y, z, w;
@@ -724,6 +749,8 @@ inline QVector4D operator*(const QMatrix4x4& matrix, const QVector4D& vector)
     return QVector4D(x, y, z, w);
 }
 
+#endif // QT_MATH3D_ASM_VEC4_TRANSFORM
+
 #endif
 
 inline QPoint operator*(const QPoint& point, const QMatrix4x4& matrix)
@@ -943,6 +970,8 @@ inline QVector3D QMatrix4x4::map(const QVector3D& point) const
     return *this * point;
 }
 
+#if !defined(QT_MATH3D_ASM_VEC3_TRANSFORM)
+
 inline QVector3D QMatrix4x4::mapVector(const QVector3D& vector) const
 {
     if (flagBits == Identity || flagBits == Translation) {
@@ -964,6 +993,8 @@ inline QVector3D QMatrix4x4::mapVector(const QVector3D& vector) const
     }
 }
 
+#endif // QT_MATH3D_ASM_VEC3_TRANSFORM
+
 #endif
 
 #ifndef QT_NO_VECTOR4D
@@ -1017,8 +1048,14 @@ QGenericMatrix<N, M, qreal> qGenericMatrixFromMatrix4x4(const QMatrix4x4& matrix
     return result;
 }
 
+#if defined(QT_MATH3D_NEON)
+#define QT_MATH3D_DEFINE_MAT4X4_ASM 1
+#include "qmath3dasm_neon.h"
+#undef QT_MATH3D_DEFINE_MAT4X4_ASM
 #endif
 
+#endif // QT_NO_MATRIX4X4
+
 QT_END_NAMESPACE
 
 QT_END_HEADER
diff --git a/tests/benchmarks/gui/math3d/qmatrix4x4/tst_qmatrix4x4.cpp b/tests/benchmarks/gui/math3d/qmatrix4x4/tst_qmatrix4x4.cpp
index e962198..1b819d8 100644
--- a/tests/benchmarks/gui/math3d/qmatrix4x4/tst_qmatrix4x4.cpp
+++ b/tests/benchmarks/gui/math3d/qmatrix4x4/tst_qmatrix4x4.cpp
@@ -65,9 +65,21 @@ private slots:
     void mapVector2D_data();
     void mapVector2D();
 
+    void mapVector4D_data();
+    void mapVector4D();
+
     void mapVectorDirect_data();
     void mapVectorDirect();
 
+    void mapVector4DDirect_data();
+    void mapVector4DDirect();
+
+    void mapNormalVector3D_data();
+    void mapNormalVector3D();
+
+    void mapNormalVectorDirect_data();
+    void mapNormalVectorDirect();
+
     void compareTranslate_data();
     void compareTranslate();
 
@@ -102,6 +114,12 @@ static qreal const generalValues[16] =
      9.0f, 10.0f, 11.0f, 12.0f,
      13.0f, 14.0f, 15.0f, 16.0f};
 
+static qreal const generalValuesNoW[16] =
+    {1.0f, 2.0f, 3.0f, 4.0f,
+     5.0f, 6.0f, 7.0f, 8.0f,
+     9.0f, 10.0f, 11.0f, 12.0f,
+     0.0f, 0.0f, 0.0f, 1.0f};
+
 void tst_QMatrix4x4::multiply_data()
 {
     QTest::addColumn<QMatrix4x4>("m1");
@@ -196,6 +214,7 @@ void tst_QMatrix4x4::mapVector3D_data()
 
     QTest::newRow("identity") << QMatrix4x4();
     QTest::newRow("general") << QMatrix4x4(generalValues);
+    QTest::newRow("general-no-w") << QMatrix4x4(generalValuesNoW);
 
     QMatrix4x4 t1;
     t1.translate(-100.5f, 64.0f, 75.25f);
@@ -255,6 +274,30 @@ void tst_QMatrix4x4::mapVector2D()
     vresult2 = result;
 }
 
+QVector4D vresult4;
+
+void tst_QMatrix4x4::mapVector4D_data()
+{
+    mapVector3D_data();
+}
+void tst_QMatrix4x4::mapVector4D()
+{
+    QFETCH(QMatrix4x4, m1);
+
+    QVector4D v(10.5f, -2.0f, 3.0f, 6.0f);
+    QVector4D result;
+
+    m1.optimize();
+
+    QBENCHMARK {
+        result = m1 * v;
+    }
+
+    // Force the result to be stored so the compiler doesn't
+    // optimize away the contents of the benchmark loop.
+    vresult4 = result;
+}
+
 // Use a direct naive multiplication algorithm.  This is used
 // to compare against the optimized routines to see if they are
 // actually faster than the naive implementation.
@@ -281,6 +324,76 @@ void tst_QMatrix4x4::mapVectorDirect()
         result[1] /= result[3];
         result[2] /= result[3];
     }
+
+    vresult = QVector3D(result[0], result[1], result[2]);
+}
+
+void tst_QMatrix4x4::mapVector4DDirect_data()
+{
+    mapVector3D_data();
+}
+void tst_QMatrix4x4::mapVector4DDirect()
+{
+    QFETCH(QMatrix4x4, m1);
+
+    const qreal *m1data = m1.constData();
+    qreal v[4] = {10.5f, -2.0f, 3.0f, 6.0f};
+    qreal result[4];
+
+    QBENCHMARK {
+        for (int row = 0; row < 4; ++row) {
+            result[row] = 0.0f;
+            for (int col = 0; col < 4; ++col) {
+                result[row] += m1data[col * 4 + row] * v[col];
+            }
+        }
+    }
+
+    vresult4 = QVector4D(result[0], result[1], result[2], result[3]);
+}
+
+void tst_QMatrix4x4::mapNormalVector3D_data()
+{
+    mapVector3D_data();
+}
+void tst_QMatrix4x4::mapNormalVector3D()
+{
+    QFETCH(QMatrix4x4, m1);
+
+    QVector3D v(10.5f, -2.0f, 3.0f);
+    QVector3D result;
+
+    m1.optimize();
+
+    QBENCHMARK {
+        result = m1.mapVector(v);
+    }
+
+    vresult = result;
+}
+
+void tst_QMatrix4x4::mapNormalVectorDirect_data()
+{
+    mapVector3D_data();
+}
+void tst_QMatrix4x4::mapNormalVectorDirect()
+{
+    QFETCH(QMatrix4x4, m1);
+
+    const qreal *m1data = m1.constData();
+    qreal v[4] = {10.5f, -2.0f, 3.0f, 1.0f};
+    qreal result[4];
+
+    QBENCHMARK {
+        for (int row = 0; row < 3; ++row) {
+            result[row] = 0.0f;
+            for (int col = 0; col < 3; ++col) {
+                result[row] += m1data[col * 4 + row] * v[col];
+            }
+        }
+    }
+
+    vresult = QVector3D(result[0], result[1], result[2]);
 }
 
 // Compare the performance of QTransform::translate() to
