/*
 * Copyright 2011 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#include "Benchmark.h"
#include "SkMatrix.h"
#include "SkMatrixUtils.h"
#include "SkRandom.h"
#include "SkString.h"

class MatrixBench : public Benchmark {
    SkString    fName;
public:
    MatrixBench(const char name[])  {
        fName.printf("matrix_%s", name);
    }

    virtual bool isSuitableFor(Backend backend) SK_OVERRIDE {
        return backend == kNonRendering_Backend;
    }

    virtual void performTest() = 0;

protected:
    virtual int mulLoopCount() const { return 1; }

    virtual const char* onGetName() {
        return fName.c_str();
    }

    virtual void onDraw(const int loops, SkCanvas*) {
        for (int i = 0; i < loops; i++) {
            this->performTest();
        }
    }

private:
    typedef Benchmark INHERITED;
};


class EqualsMatrixBench : public MatrixBench {
public:
    EqualsMatrixBench() : INHERITED("equals") {}
protected:
    virtual void performTest() {
        SkMatrix m0, m1, m2;

        m0.reset();
        m1.reset();
        m2.reset();

        // xor into a volatile prevents these comparisons from being optimized away.
        volatile bool junk = false;
        junk ^= (m0 == m1);
        junk ^= (m1 == m2);
        junk ^= (m2 == m0);
    }
private:
    typedef MatrixBench INHERITED;
};

class ScaleMatrixBench : public MatrixBench {
public:
    ScaleMatrixBench() : INHERITED("scale") {
        fSX = fSY = 1.5f;
        fM0.reset();
        fM1.setScale(fSX, fSY);
        fM2.setTranslate(fSX, fSY);
    }
protected:
    virtual void performTest() {
        SkMatrix m;
        m = fM0; m.preScale(fSX, fSY);
        m = fM1; m.preScale(fSX, fSY);
        m = fM2; m.preScale(fSX, fSY);
    }
private:
    SkMatrix fM0, fM1, fM2;
    SkScalar fSX, fSY;
    typedef MatrixBench INHERITED;
};

// having unknown values in our arrays can throw off the timing a lot, perhaps
// handling NaN values is a lot slower. Anyway, this guy is just meant to put
// reasonable values in our arrays.
template <typename T> void init9(T array[9]) {
    SkRandom rand;
    for (int i = 0; i < 9; i++) {
        array[i] = rand.nextSScalar1();
    }
}

// Test the performance of setConcat() non-perspective case:
// using floating point precision only.
class FloatConcatMatrixBench : public MatrixBench {
public:
    FloatConcatMatrixBench() : INHERITED("concat_floatfloat") {
        init9(mya);
        init9(myb);
        init9(myr);
    }
protected:
    virtual int mulLoopCount() const { return 4; }

    static inline void muladdmul(float a, float b, float c, float d,
                                   float* result) {
      *result = a * b + c * d;
    }
    virtual void performTest() {
        const float* a = mya;
        const float* b = myb;
        float* r = myr;
        muladdmul(a[0], b[0], a[1], b[3], &r[0]);
        muladdmul(a[0], b[1], a[1], b[4], &r[1]);
        muladdmul(a[0], b[2], a[1], b[5], &r[2]);
        r[2] += a[2];
        muladdmul(a[3], b[0], a[4], b[3], &r[3]);
        muladdmul(a[3], b[1], a[4], b[4], &r[4]);
        muladdmul(a[3], b[2], a[4], b[5], &r[5]);
        r[5] += a[5];
        r[6] = r[7] = 0.0f;
        r[8] = 1.0f;
    }
private:
    float mya [9];
    float myb [9];
    float myr [9];
    typedef MatrixBench INHERITED;
};

static inline float SkDoubleToFloat(double x) {
    return static_cast<float>(x);
}

// Test the performance of setConcat() non-perspective case:
// using floating point precision but casting up to float for
// intermediate results during computations.
class FloatDoubleConcatMatrixBench : public MatrixBench {
public:
    FloatDoubleConcatMatrixBench() : INHERITED("concat_floatdouble") {
        init9(mya);
        init9(myb);
        init9(myr);
    }
protected:
    virtual int mulLoopCount() const { return 4; }

    static inline void muladdmul(float a, float b, float c, float d,
                                   float* result) {
      *result = SkDoubleToFloat((double)a * b + (double)c * d);
    }
    virtual void performTest() {
        const float* a = mya;
        const float* b = myb;
        float* r = myr;
        muladdmul(a[0], b[0], a[1], b[3], &r[0]);
        muladdmul(a[0], b[1], a[1], b[4], &r[1]);
        muladdmul(a[0], b[2], a[1], b[5], &r[2]);
        r[2] += a[2];
        muladdmul(a[3], b[0], a[4], b[3], &r[3]);
        muladdmul(a[3], b[1], a[4], b[4], &r[4]);
        muladdmul(a[3], b[2], a[4], b[5], &r[5]);
        r[5] += a[5];
        r[6] = r[7] = 0.0f;
        r[8] = 1.0f;
    }
private:
    float mya [9];
    float myb [9];
    float myr [9];
    typedef MatrixBench INHERITED;
};

// Test the performance of setConcat() non-perspective case:
// using double precision only.
class DoubleConcatMatrixBench : public MatrixBench {
public:
    DoubleConcatMatrixBench() : INHERITED("concat_double") {
        init9(mya);
        init9(myb);
        init9(myr);
    }
protected:
    virtual int mulLoopCount() const { return 4; }

    static inline void muladdmul(double a, double b, double c, double d,
                                   double* result) {
      *result = a * b + c * d;
    }
    virtual void performTest() {
        const double* a = mya;
        const double* b = myb;
        double* r = myr;
        muladdmul(a[0], b[0], a[1], b[3], &r[0]);
        muladdmul(a[0], b[1], a[1], b[4], &r[1]);
        muladdmul(a[0], b[2], a[1], b[5], &r[2]);
        r[2] += a[2];
        muladdmul(a[3], b[0], a[4], b[3], &r[3]);
        muladdmul(a[3], b[1], a[4], b[4], &r[4]);
        muladdmul(a[3], b[2], a[4], b[5], &r[5]);
        r[5] += a[5];
        r[6] = r[7] = 0.0;
        r[8] = 1.0;
    }
private:
    double mya [9];
    double myb [9];
    double myr [9];
    typedef MatrixBench INHERITED;
};

class GetTypeMatrixBench : public MatrixBench {
public:
    GetTypeMatrixBench()
        : INHERITED("gettype") {
        fArray[0] = (float) fRnd.nextS();
        fArray[1] = (float) fRnd.nextS();
        fArray[2] = (float) fRnd.nextS();
        fArray[3] = (float) fRnd.nextS();
        fArray[4] = (float) fRnd.nextS();
        fArray[5] = (float) fRnd.nextS();
        fArray[6] = (float) fRnd.nextS();
        fArray[7] = (float) fRnd.nextS();
        fArray[8] = (float) fRnd.nextS();
    }
protected:
    // Putting random generation of the matrix inside performTest()
    // would help us avoid anomalous runs, but takes up 25% or
    // more of the function time.
    virtual void performTest() {
        fMatrix.setAll(fArray[0], fArray[1], fArray[2],
                       fArray[3], fArray[4], fArray[5],
                       fArray[6], fArray[7], fArray[8]);
        // xoring into a volatile prevents the compiler from optimizing these away
        volatile int junk = 0;
        junk ^= (fMatrix.getType());
        fMatrix.dirtyMatrixTypeCache();
        junk ^= (fMatrix.getType());
        fMatrix.dirtyMatrixTypeCache();
        junk ^= (fMatrix.getType());
        fMatrix.dirtyMatrixTypeCache();
        junk ^= (fMatrix.getType());
        fMatrix.dirtyMatrixTypeCache();
        junk ^= (fMatrix.getType());
        fMatrix.dirtyMatrixTypeCache();
        junk ^= (fMatrix.getType());
        fMatrix.dirtyMatrixTypeCache();
        junk ^= (fMatrix.getType());
        fMatrix.dirtyMatrixTypeCache();
        junk ^= (fMatrix.getType());
    }
private:
    SkMatrix fMatrix;
    float fArray[9];
    SkRandom fRnd;
    typedef MatrixBench INHERITED;
};

class ScaleTransMixedMatrixBench : public MatrixBench {
 public:
    ScaleTransMixedMatrixBench() : INHERITED("scaletrans_mixed") {
        fMatrix.setAll(fRandom.nextSScalar1(), fRandom.nextSScalar1(), fRandom.nextSScalar1(),
                       fRandom.nextSScalar1(), fRandom.nextSScalar1(), fRandom.nextSScalar1(),
                       fRandom.nextSScalar1(), fRandom.nextSScalar1(), fRandom.nextSScalar1());
        int i;
        for (i = 0; i < kCount; i++) {
            fSrc[i].fX = fRandom.nextSScalar1();
            fSrc[i].fY = fRandom.nextSScalar1();
            fDst[i].fX = fRandom.nextSScalar1();
            fDst[i].fY = fRandom.nextSScalar1();
        }
    }
 protected:
    virtual void performTest() {
        SkPoint* dst = fDst;
        const SkPoint* src = fSrc;
        int count = kCount;
        float mx = fMatrix[SkMatrix::kMScaleX];
        float my = fMatrix[SkMatrix::kMScaleY];
        float tx = fMatrix[SkMatrix::kMTransX];
        float ty = fMatrix[SkMatrix::kMTransY];
        do {
            dst->fY = SkScalarMulAdd(src->fY, my, ty);
            dst->fX = SkScalarMulAdd(src->fX, mx, tx);
            src += 1;
            dst += 1;
        } while (--count);
    }
 private:
    enum {
        kCount = 16
    };
    SkMatrix fMatrix;
    SkPoint fSrc [kCount];
    SkPoint fDst [kCount];
    SkRandom fRandom;
    typedef MatrixBench INHERITED;
};

class ScaleTransDoubleMatrixBench : public MatrixBench {
 public:
    ScaleTransDoubleMatrixBench() : INHERITED("scaletrans_double") {
        init9(fMatrix);
        int i;
        for (i = 0; i < kCount; i++) {
            fSrc[i].fX = fRandom.nextSScalar1();
            fSrc[i].fY = fRandom.nextSScalar1();
            fDst[i].fX = fRandom.nextSScalar1();
            fDst[i].fY = fRandom.nextSScalar1();
        }
    }
 protected:
    virtual void performTest() {
        SkPoint* dst = fDst;
        const SkPoint* src = fSrc;
        int count = kCount;
        // As doubles, on Z600 Linux systems this is 2.5x as expensive as mixed mode
        float mx = (float) fMatrix[SkMatrix::kMScaleX];
        float my = (float) fMatrix[SkMatrix::kMScaleY];
        float tx = (float) fMatrix[SkMatrix::kMTransX];
        float ty = (float) fMatrix[SkMatrix::kMTransY];
        do {
            dst->fY = src->fY * my + ty;
            dst->fX = src->fX * mx + tx;
            src += 1;
            dst += 1;
        } while (--count);
    }
 private:
    enum {
        kCount = 16
    };
    double fMatrix [9];
    SkPoint fSrc [kCount];
    SkPoint fDst [kCount];
    SkRandom fRandom;
    typedef MatrixBench INHERITED;
};

class DecomposeMatrixBench : public MatrixBench {
public:
    DecomposeMatrixBench() : INHERITED("decompose") {}

protected:
    virtual void onPreDraw() {
        for (int i = 0; i < 10; ++i) {
            SkScalar rot0 = (fRandom.nextBool()) ? fRandom.nextRangeF(-180, 180) : 0.0f;
            SkScalar sx = fRandom.nextRangeF(-3000.f, 3000.f);
            SkScalar sy = (fRandom.nextBool()) ? fRandom.nextRangeF(-3000.f, 3000.f) : sx;
            SkScalar rot1 = fRandom.nextRangeF(-180, 180);
            fMatrix[i].setRotate(rot0);
            fMatrix[i].postScale(sx, sy);
            fMatrix[i].postRotate(rot1);
        }
    }
    virtual void performTest() {
        SkPoint rotation1, scale, rotation2;
        for (int i = 0; i < 10; ++i) {
            (void) SkDecomposeUpper2x2(fMatrix[i], &rotation1, &scale, &rotation2);
        }
    }
private:
    SkMatrix fMatrix[10];
    SkRandom fRandom;
    typedef MatrixBench INHERITED;
};

class InvertMapRectMatrixBench : public MatrixBench {
public:
    InvertMapRectMatrixBench(const char* name, int flags)
        : INHERITED(name)
        , fFlags(flags) {
        fMatrix.reset();
        fIteration = 0;
        if (flags & kScale_Flag) {
            fMatrix.postScale(1.5f, 2.5f);
        }
        if (flags & kTranslate_Flag) {
            fMatrix.postTranslate(1.5f, 2.5f);
        }
        if (flags & kRotate_Flag) {
            fMatrix.postRotate(45.0f);
        }
        if (flags & kPerspective_Flag) {
            fMatrix.setPerspX(1.5f);
            fMatrix.setPerspY(2.5f);
        }
        if (0 == (flags & kUncachedTypeMask_Flag)) {
            fMatrix.getType();
        }
    }
    enum Flag {
        kScale_Flag             = 0x01,
        kTranslate_Flag         = 0x02,
        kRotate_Flag            = 0x04,
        kPerspective_Flag       = 0x08,
        kUncachedTypeMask_Flag  = 0x10,
    };
protected:
    virtual void performTest() {
        if (fFlags & kUncachedTypeMask_Flag) {
            // This will invalidate the typemask without
            // changing the matrix.
            fMatrix.setPerspX(fMatrix.getPerspX());
        }
        SkMatrix inv;
        bool invertible = fMatrix.invert(&inv);
        SkASSERT(invertible);
        SkRect transformedRect;
        // an arbitrary, small, non-zero rect to transform
        SkRect srcRect = SkRect::MakeWH(SkIntToScalar(10), SkIntToScalar(10));
        if (invertible) {
            inv.mapRect(&transformedRect, srcRect);
        }
    }
private:
    SkMatrix fMatrix;
    int fFlags;
    unsigned fIteration;
    typedef MatrixBench INHERITED;
};

///////////////////////////////////////////////////////////////////////////////

DEF_BENCH( return new EqualsMatrixBench(); )
DEF_BENCH( return new ScaleMatrixBench(); )
DEF_BENCH( return new FloatConcatMatrixBench(); )
DEF_BENCH( return new FloatDoubleConcatMatrixBench(); )
DEF_BENCH( return new DoubleConcatMatrixBench(); )
DEF_BENCH( return new GetTypeMatrixBench(); )
DEF_BENCH( return new DecomposeMatrixBench(); )

DEF_BENCH( return new InvertMapRectMatrixBench("invert_maprect_identity", 0); )

DEF_BENCH(return new InvertMapRectMatrixBench(
                                  "invert_maprect_rectstaysrect",
                                  InvertMapRectMatrixBench::kScale_Flag |
                                  InvertMapRectMatrixBench::kTranslate_Flag); )

DEF_BENCH(return new InvertMapRectMatrixBench(
                                  "invert_maprect_translate",
                                  InvertMapRectMatrixBench::kTranslate_Flag); )

DEF_BENCH(return new InvertMapRectMatrixBench(
                                  "invert_maprect_nonpersp",
                                  InvertMapRectMatrixBench::kScale_Flag |
                                  InvertMapRectMatrixBench::kRotate_Flag |
                                  InvertMapRectMatrixBench::kTranslate_Flag); )

DEF_BENCH( return new InvertMapRectMatrixBench(
                               "invert_maprect_persp",
                               InvertMapRectMatrixBench::kPerspective_Flag); )

DEF_BENCH( return new InvertMapRectMatrixBench(
                           "invert_maprect_typemask_rectstaysrect",
                           InvertMapRectMatrixBench::kUncachedTypeMask_Flag |
                           InvertMapRectMatrixBench::kScale_Flag |
                           InvertMapRectMatrixBench::kTranslate_Flag); )

DEF_BENCH( return new InvertMapRectMatrixBench(
                           "invert_maprect_typemask_nonpersp",
                           InvertMapRectMatrixBench::kUncachedTypeMask_Flag |
                           InvertMapRectMatrixBench::kScale_Flag |
                           InvertMapRectMatrixBench::kRotate_Flag |
                           InvertMapRectMatrixBench::kTranslate_Flag); )

DEF_BENCH( return new ScaleTransMixedMatrixBench(); )
DEF_BENCH( return new ScaleTransDoubleMatrixBench(); )