4-at-a-time SkPMColor -> SkPMFloat API.
authormtklein <mtklein@chromium.org>
Thu, 5 Mar 2015 19:31:59 +0000 (11:31 -0800)
committerCommit bot <commit-bot@chromium.org>
Thu, 5 Mar 2015 19:31:59 +0000 (11:31 -0800)
Please see if this looks usable.  It may even give a perf boost if you use it, even without custom implementations for each instruction set.

I've been trying this morning to beat this naive loop implementation, but so far no luck with either _SSE2.h or _SSSE3.h.  It's possible this is an artifact of the microbenchmark, because we're not doing anything between the conversions.  I'd like to see how this fits into real code, what assembly's generated, what the hot spots are, etc.

I've updated the tests to test these new APIs, and splintered off a pair of new benchmarks that use the new APIs.  This required some minor rejiggering in the benches.

BUG=skia:

Review URL: https://codereview.chromium.org/978213003

bench/PMFloatBench.cpp
src/core/SkPMFloat.h
tests/PMFloatTest.cpp

index ee4914a..1da667f 100644 (file)
@@ -12,10 +12,21 @@ static uint32_t lcg_rand(uint32_t* seed) {
     return *seed;
 }
 
+// I'm having better luck getting these to constant-propagate away as template parameters.
+template <bool kClamp, bool kWide>
 struct PMFloatBench : public Benchmark {
-    explicit PMFloatBench(bool clamp) : fClamp(clamp) {}
+    PMFloatBench() {}
 
-    const char* onGetName() SK_OVERRIDE { return fClamp ? "SkPMFloat_clamp" : "SkPMFloat_get"; }
+    const char* onGetName() SK_OVERRIDE {
+        switch (kClamp << 1 | kWide) {
+            case 0: return "SkPMFloat_get_1x";
+            case 1: return "SkPMFloat_get_4x";
+            case 2: return "SkPMFloat_clamp_1x";
+            case 3: return "SkPMFloat_clamp_4x";
+        }
+        SkFAIL("unreachable");
+        return "oh bother";
+    }
     bool isSuitableFor(Backend backend) SK_OVERRIDE { return backend == kNonRendering_Backend; }
 
     void onDraw(const int loops, SkCanvas* canvas) SK_OVERRIDE {
@@ -23,21 +34,47 @@ struct PMFloatBench : public Benchmark {
         uint32_t junk = 0;
         uint32_t seed = 0;
         for (int i = 0; i < loops; i++) {
+            SkPMColor colors[4];
         #ifdef SK_DEBUG
-            // Our SkASSERTs will remind us that it's technically required that we premultiply.
-            SkPMColor c = SkPreMultiplyColor(lcg_rand(&seed));
+            for (int i = 0; i < 4; i++) {
+                // Our SkASSERTs will remind us that it's technically required that we premultiply.
+                colors[i] = SkPreMultiplyColor(lcg_rand(&seed));
+            }
         #else
             // But it's a lot faster not to, and this code won't really mind the non-PM colors.
-            SkPMColor c = lcg_rand(&seed);
+            (void)lcg_rand(&seed);
+            colors[0] = seed + 0;
+            colors[1] = seed + 1;
+            colors[2] = seed + 2;
+            colors[3] = seed + 3;
         #endif
-            SkPMFloat pmf = SkPMFloat::FromPMColor(c);
-            SkPMColor back = fClamp ? pmf.clamped() : pmf.get();
-            junk ^= back;
+
+            SkPMFloat floats[4];
+            if (kWide) {
+                SkPMFloat::From4PMColors(floats, colors);
+            } else {
+                for (int i = 0; i < 4; i++) {
+                    floats[i] = SkPMFloat::FromPMColor(colors[i]);
+                }
+            }
+
+            SkPMColor back[4];
+            switch (kClamp << 1 | kWide) {
+                case 0: for (int i = 0; i < 4; i++) { back[i] = floats[i].get(); }     break;
+                case 1: SkPMFloat::To4PMColors(back, floats);                          break;
+                case 2: for (int i = 0; i < 4; i++) { back[i] = floats[i].clamped(); } break;
+                case 3: SkPMFloat::ClampTo4PMColors(back, floats);                     break;
+            }
+            for (int i = 0; i < 4; i++) {
+                junk ^= back[i];
+            }
         }
         blackhole ^= junk;
     }
-
-    bool fClamp;
 };
-DEF_BENCH(return new PMFloatBench( true);)
-DEF_BENCH(return new PMFloatBench(false);)
+
+// Extra () help DEF_BENCH not get confused by the comma inside the <>.
+DEF_BENCH(return (new PMFloatBench< true,  true>);)
+DEF_BENCH(return (new PMFloatBench<false,  true>);)
+DEF_BENCH(return (new PMFloatBench< true, false>);)
+DEF_BENCH(return (new PMFloatBench<false, false>);)
index 444cb78..2e06ea9 100644 (file)
@@ -12,6 +12,12 @@ public:
     static SkPMFloat FromPMColor(SkPMColor c) { return SkPMFloat(c); }
     static SkPMFloat FromARGB(float a, float r, float g, float b) { return SkPMFloat(a,r,g,b); }
 
+    // May be more efficient than one at a time.  No special alignment assumed for SkPMColors.
+    static void From4PMColors(SkPMFloat floats[4], const SkPMColor colors[4]) {
+        // TODO: specialize
+        for (int i = 0; i < 4; i++) { floats[i] = FromPMColor(colors[i]); }
+    }
+
     explicit SkPMFloat(SkPMColor);
     SkPMFloat(float a, float r, float g, float b) {
         // TODO: faster when specialized?
@@ -44,6 +50,16 @@ public:
     SkPMColor     get() const;  // May SkASSERT(this->isValid()).  Some implementations may clamp.
     SkPMColor clamped() const;  // Will clamp all values to [0, 255].  Then may assert isValid().
 
+    // 4-at-a-time versions of get() and clamped().  Like From4PMColors(), no alignment assumed.
+    static void To4PMColors(SkPMColor colors[4], const SkPMFloat floats[4]) {
+        // TODO: specialize
+        for (int i = 0; i < 4; i++) { colors[i] = floats[i].get(); }
+    }
+    static void ClampTo4PMColors(SkPMColor colors[4], const SkPMFloat floats[4]) {
+        // TODO: specialize
+        for (int i = 0; i < 4; i++) { colors[i] = floats[i].clamped(); }
+    }
+
     bool isValid() const {
         return this->a() >= 0 && this->a() <= 255
             && this->r() >= 0 && this->r() <= this->a()
index 3f5e1c0..13c0a12 100644 (file)
@@ -29,4 +29,20 @@ DEF_TEST(SkPMFloat, r) {
     REPORTER_ASSERT(r, SkScalarNearlyEqual(38.25f, scaled.r()));
     REPORTER_ASSERT(r, SkScalarNearlyEqual( 0.25f, scaled.g()));
     REPORTER_ASSERT(r, SkScalarNearlyEqual( 0.00f, scaled.b()));
+
+    // Test 4-at-a-time conversions.
+    SkPMColor colors[4] = { 0xFF000000, 0xFFFF0000, 0xFF00FF00, 0xFF0000FF };
+    SkPMFloat floats[4];
+    SkPMFloat::From4PMColors(floats, colors);
+
+    SkPMColor back[4];
+    SkPMFloat::To4PMColors(back, floats);
+    for (int i = 0; i < 4; i++) {
+        REPORTER_ASSERT(r, back[i] == colors[i]);
+    }
+
+    SkPMFloat::ClampTo4PMColors(back, floats);
+    for (int i = 0; i < 4; i++) {
+        REPORTER_ASSERT(r, back[i] == colors[i]);
+    }
 }