generalize skvm pixel unpacking

Add a PixelFormat struct and use it to load/gather_unorm().  This handles
loading all pixel formats that are unorm and can fit in 32 bits, with
float and larger formats to follow.  That means this adds SkVMBlitter
support for reading A8, G8, 4444, R8G8, A16_unorm, and R16G16_unorm.

Next step will do the same for stores, allowing arbitrary destinations.

Change-Id: Iabbf6171ee6d5abb44cf131eda6647980767c396
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/302252
Commit-Queue: Mike Klein <mtklein@google.com>
Commit-Queue: Mike Reed <reed@google.com>
Auto-Submit: Mike Klein <mtklein@google.com>
Reviewed-by: Mike Reed <reed@google.com>
diff --git a/src/core/SkVM.cpp b/src/core/SkVM.cpp
index a23aa00..2c95b69 100644
--- a/src/core/SkVM.cpp
+++ b/src/core/SkVM.cpp
@@ -1115,29 +1115,79 @@
         return round(mul(x, limit));
     }
 
-    Color Builder::unpack_1010102(I32 rgba) {
+    bool SkColorType_to_PixelFormat(SkColorType ct, PixelFormat* f) {
+        switch (ct) {
+            case kUnknown_SkColorType: SkASSERT(false); return false;
+
+            // TODO: float and >32-bit formats
+            case kRGBA_F16Norm_SkColorType:
+            case kRGBA_F16_SkColorType:
+            case kRGBA_F32_SkColorType:
+            case kA16_float_SkColorType:
+            case kR16G16_float_SkColorType:
+            case kR16G16B16A16_unorm_SkColorType: return false;
+
+            case kAlpha_8_SkColorType: *f = {0,0,0,8, 0,0,0,0}; return true;
+            case kGray_8_SkColorType:  *f = {8,8,8,0, 0,0,0,0}; return true;  // Gray is subtle!
+
+            case kRGB_565_SkColorType:   *f = {5,6,5,0, 11,5,0,0}; return true;  // Yes, it's BGR.
+            case kARGB_4444_SkColorType: *f = {4,4,4,4, 12,8,4,0}; return true;  // Yes, it's ABGR.
+
+            case kRGBA_8888_SkColorType:  *f = {8,8,8,8,  0,8,16,24}; return true;
+            case kRGB_888x_SkColorType:   *f = {8,8,8,0,  0,8,16,32}; return true;  // N.B. 4-byte.
+            case kBGRA_8888_SkColorType:  *f = {8,8,8,8, 16,8, 0,24}; return true;
+
+            case kRGBA_1010102_SkColorType: *f = {10,10,10,2,  0,10,20,30}; return true;
+            case kBGRA_1010102_SkColorType: *f = {10,10,10,2, 20,10, 0,30}; return true;
+            case kRGB_101010x_SkColorType:  *f = {10,10,10,0,  0,10,20, 0}; return true;
+            case kBGR_101010x_SkColorType:  *f = {10,10,10,0, 20,10, 0, 0}; return true;
+
+            case kR8G8_unorm_SkColorType:   *f = { 8, 8,0, 0, 0, 8,0,0}; return true;
+            case kR16G16_unorm_SkColorType: *f = {16,16,0, 0, 0,16,0,0}; return true;
+            case kA16_unorm_SkColorType:    *f = { 0, 0,0,16, 0, 0,0,0}; return true;
+        }
+        return false;
+    }
+
+    static Color unpack_unorm(PixelFormat f, I32 x) {
         return {
-            from_unorm(10, extract(rgba,  0, 0x3ff)),
-            from_unorm(10, extract(rgba, 10, 0x3ff)),
-            from_unorm(10, extract(rgba, 20, 0x3ff)),
-            from_unorm( 2, extract(rgba, 30, 0x3  )),
+            f.r_bits ? from_unorm(f.r_bits, extract(x, f.r_shift, (1<<f.r_bits)-1)) : x->splat(0.f),
+            f.g_bits ? from_unorm(f.g_bits, extract(x, f.g_shift, (1<<f.g_bits)-1)) : x->splat(0.f),
+            f.b_bits ? from_unorm(f.b_bits, extract(x, f.b_shift, (1<<f.b_bits)-1)) : x->splat(0.f),
+            f.a_bits ? from_unorm(f.a_bits, extract(x, f.a_shift, (1<<f.a_bits)-1)) : x->splat(1.f),
         };
     }
-    Color Builder::unpack_8888(I32 rgba) {
-        return {
-            from_unorm(8, extract(rgba,  0, 0xff)),
-            from_unorm(8, extract(rgba,  8, 0xff)),
-            from_unorm(8, extract(rgba, 16, 0xff)),
-            from_unorm(8, extract(rgba, 24, 0xff)),
-        };
+
+    static int byte_size(PixelFormat f) {
+        // What's the highest bit we read?
+        int bits = std::max(f.r_bits + f.r_shift,
+                   std::max(f.g_bits + f.g_shift,
+                   std::max(f.b_bits + f.b_shift,
+                            f.a_bits + f.a_shift)));
+        // Round up to bytes.
+        return (bits + 7) / 8;
     }
-    Color Builder::unpack_565(I32 bgr) {
-        return {
-            from_unorm(5, extract(bgr, 11, 0b011'111)),
-            from_unorm(6, extract(bgr,  5, 0b111'111)),
-            from_unorm(5, extract(bgr,  0, 0b011'111)),
-            splat(1.0f),
-        };
+
+    Color Builder::load(PixelFormat f, Arg ptr) {
+        switch (byte_size(f)) {
+            case 1: return unpack_unorm(f, load8 (ptr));
+            case 2: return unpack_unorm(f, load16(ptr));
+            case 4: return unpack_unorm(f, load32(ptr));
+            // TODO: 8,16
+            default: SkUNREACHABLE;
+        }
+        return {};
+    }
+
+    Color Builder::gather(PixelFormat f, Arg ptr, int offset, I32 index) {
+        switch (byte_size(f)) {
+            case 1: return unpack_unorm(f, gather8 (ptr, offset, index));
+            case 2: return unpack_unorm(f, gather16(ptr, offset, index));
+            case 4: return unpack_unorm(f, gather32(ptr, offset, index));
+            // TODO: 8,16
+            default: SkUNREACHABLE;
+        }
+        return {};
     }
 
     void Builder::unpremul(F32* r, F32* g, F32* b, F32 a) {
diff --git a/src/core/SkVM.h b/src/core/SkVM.h
index b8c8f69..5b27da1 100644
--- a/src/core/SkVM.h
+++ b/src/core/SkVM.h
@@ -504,6 +504,12 @@
         }
     };
 
+    struct PixelFormat {
+        int r_bits,  g_bits,  b_bits,  a_bits,
+            r_shift, g_shift, b_shift, a_shift;
+    };
+    bool SkColorType_to_PixelFormat(SkColorType, PixelFormat*);
+
     SK_BEGIN_REQUIRE_DENSE
     struct Instruction {
         Op  op;         // v* = op(x,y,z,imm), where * == index of this Instruction.
@@ -711,9 +717,11 @@
         F32 from_unorm(int bits, I32);   // E.g. from_unorm(8, x) -> x * (1/255.0f)
         I32   to_unorm(int bits, F32);   // E.g.   to_unorm(8, x) -> round(x * 255)
 
-        Color unpack_1010102(I32 rgba);
-        Color unpack_8888   (I32 rgba);
-        Color unpack_565    (I32 bgr );  // bottom 16 bits
+        Color   load(PixelFormat, Arg ptr);
+        Color gather(PixelFormat, Arg ptr, int offset, I32 index);
+        Color gather(PixelFormat f, Uniform u, I32 index) {
+            return gather(f, u.ptr, u.offset, index);
+        }
 
         void   premul(F32* r, F32* g, F32* b, F32 a);
         void unpremul(F32* r, F32* g, F32* b, F32 a);
@@ -1033,9 +1041,12 @@
     static inline F32 from_unorm(int bits, I32 x) { return x->from_unorm(bits,x); }
     static inline I32   to_unorm(int bits, F32 x) { return x->  to_unorm(bits,x); }
 
-    static inline  Color unpack_1010102(I32 rgba) { return rgba->unpack_1010102(rgba); }
-    static inline  Color unpack_8888   (I32 rgba) { return rgba->unpack_8888   (rgba); }
-    static inline  Color unpack_565    (I32 bgr ) { return bgr ->unpack_565    (bgr ); }
+    static inline Color gather(PixelFormat f, Arg p, int off, I32 ix) {
+        return ix->gather(f,p,off,ix);
+    }
+    static inline Color gather(PixelFormat f, Uniform u, I32 ix) {
+        return ix->gather(f,u,ix);
+    }
 
     static inline void   premul(F32* r, F32* g, F32* b, F32 a) { a->  premul(r,g,b,a); }
     static inline void unpremul(F32* r, F32* g, F32* b, F32 a) { a->unpremul(r,g,b,a); }
diff --git a/src/core/SkVMBlitter.cpp b/src/core/SkVMBlitter.cpp
index 196fae8..620f535 100644
--- a/src/core/SkVMBlitter.cpp
+++ b/src/core/SkVMBlitter.cpp
@@ -268,12 +268,14 @@
                                        from_unorm(8, p->load8(p->varying<uint8_t>()));
                                        break;
 
-                case Coverage::MaskLCD16:
+                case Coverage::MaskLCD16: {
                     SkASSERT(dst_loaded);
-                    *cov = unpack_565(p->load16(p->varying<uint16_t>()));
+                    skvm::PixelFormat fmt;
+                    SkAssertResult(SkColorType_to_PixelFormat(kRGB_565_SkColorType, &fmt));
+                    *cov = p->load(fmt, p->varying<uint16_t>());
                     cov->a = select(src.a < dst.a, min(cov->r, min(cov->g, cov->b))
                                                  , max(cov->r, max(cov->g, cov->b)));
-                    break;
+                } break;
             }
 
             if (params.clip) {
@@ -310,28 +312,10 @@
 
         // Load up the destination color.
         SkDEBUGCODE(dst_loaded = true;)
-        switch (params.dst.colorType()) {
-            default: SkUNREACHABLE;
-            case kRGB_565_SkColorType: dst = unpack_565(p->load16(dst_ptr));
-                                       break;
+        skvm::PixelFormat pixelFormat;
+        SkAssertResult(SkColorType_to_PixelFormat(params.dst.colorType(), &pixelFormat));
 
-            case  kRGB_888x_SkColorType: [[fallthrough]];
-            case kRGBA_8888_SkColorType: dst = unpack_8888(p->load32(dst_ptr));
-                                         break;
-
-            case kBGRA_8888_SkColorType: dst = unpack_8888(p->load32(dst_ptr));
-                                         std::swap(dst.r, dst.b);
-                                         break;
-
-            case  kRGB_101010x_SkColorType: [[fallthrough]];
-            case kRGBA_1010102_SkColorType: dst = unpack_1010102(p->load32(dst_ptr));
-                                            break;
-
-            case  kBGR_101010x_SkColorType: [[fallthrough]];
-            case kBGRA_1010102_SkColorType: dst = unpack_1010102(p->load32(dst_ptr));
-                                            std::swap(dst.r, dst.b);
-                                            break;
-        }
+        dst = p->load(pixelFormat, dst_ptr);
 
         // When a destination is known opaque, we may assume it both starts and stays fully
         // opaque, ignoring any math that disagrees.  This sometimes trims a little work.
@@ -376,6 +360,7 @@
         }
 
         // Store back to the destination.
+        // TODO: use PixelFormat like we do for unpacking.
         switch (params.dst.colorType()) {
             default: SkUNREACHABLE;
 
diff --git a/src/shaders/SkImageShader.cpp b/src/shaders/SkImageShader.cpp
index 30927f4..77a569e 100755
--- a/src/shaders/SkImageShader.cpp
+++ b/src/shaders/SkImageShader.cpp
@@ -788,19 +788,13 @@
 
     skvm::Coord upperLocal = SkShaderBase::ApplyMatrix(p, upperInv, origLocal, uniforms);
 
-    // Bail out if sample() can't yet handle our image's color type.
-    switch (upper->colorType()) {
-        default: return {};
-        case    kGray_8_SkColorType:
-        case   kAlpha_8_SkColorType:
-        case   kRGB_565_SkColorType:
-        case  kRGB_888x_SkColorType:
-        case kRGBA_8888_SkColorType:
-        case kBGRA_8888_SkColorType:
-        case kRGBA_1010102_SkColorType:
-        case kBGRA_1010102_SkColorType:
-        case  kRGB_101010x_SkColorType:
-        case  kBGR_101010x_SkColorType: break;
+    // Bail out if sample() can't yet handle our image's color type(s).
+    skvm::PixelFormat unused;
+    if (true  && !SkColorType_to_PixelFormat(upper->colorType(), &unused)) {
+        return {};
+    }
+    if (lower && !SkColorType_to_PixelFormat(lower->colorType(), &unused)) {
+        return {};
     }
 
     // We can exploit image opacity to skip work unpacking alpha channels.
@@ -833,12 +827,14 @@
         skvm::Uniform addr;
         skvm::I32     rowBytesAsPixels;
 
-        SkColorType colorType;  // not a uniform, but needed for each texel sample,
-                                // so we store it here, since it is also dependent on
-                                // the current pixmap (level).
+        skvm::PixelFormat pixelFormat;  // not a uniform, but needed for each texel sample,
+                                        // so we store it here, since it is also dependent on
+                                        // the current pixmap (level).
     };
 
     auto setup_uniforms = [&](const SkPixmap& pm) -> Uniforms {
+        skvm::PixelFormat pixelFormat;
+        SkAssertResult(SkColorType_to_PixelFormat(pm.colorType(), &pixelFormat));
         return {
             p->uniformF(uniforms->pushF(     pm.width())),
             p->uniformF(uniforms->pushF(1.0f/pm.width())), // iff tileX == kRepeat
@@ -854,7 +850,7 @@
             uniforms->pushPtr(pm.addr()),
             p->uniform32(uniforms->push(pm.rowBytesAsPixels())),
 
-            pm.colorType(),
+            pixelFormat,
         };
     };
 
@@ -889,39 +885,10 @@
                   clamped_y = clamp(sy, 0, u.clamp_h);
 
         // Load pixels from pm.addr()[(int)sx + (int)sy*stride].
-        skvm::Uniform img = u.addr;
         skvm::I32 index = trunc(clamped_x) +
                           trunc(clamped_y) * u.rowBytesAsPixels;
-        skvm::Color c;
-        switch (u.colorType) {
-            default: SkUNREACHABLE;
+        skvm::Color c = gather(u.pixelFormat, u.addr, index);
 
-            case kGray_8_SkColorType: c.r = c.g = c.b = from_unorm(8, gather8(img, index));
-                                      c.a = p->splat(1.0f);
-                                      break;
-
-            case kAlpha_8_SkColorType: c.r = c.g = c.b = p->splat(0.0f);
-                                       c.a = from_unorm(8, gather8(img, index));
-                                       break;
-
-            case   kRGB_565_SkColorType: c = unpack_565 (gather16(img, index)); break;
-
-            case  kRGB_888x_SkColorType: [[fallthrough]];
-            case kRGBA_8888_SkColorType: c = unpack_8888(gather32(img, index));
-                                         break;
-            case kBGRA_8888_SkColorType: c = unpack_8888(gather32(img, index));
-                                         std::swap(c.r, c.b);
-                                         break;
-
-            case  kRGB_101010x_SkColorType: [[fallthrough]];
-            case kRGBA_1010102_SkColorType: c = unpack_1010102(gather32(img, index));
-                                            break;
-
-            case  kBGR_101010x_SkColorType: [[fallthrough]];
-            case kBGRA_1010102_SkColorType: c = unpack_1010102(gather32(img, index));
-                                            std::swap(c.r, c.b);
-                                            break;
-        }
         // If we know the image is opaque, jump right to alpha = 1.0f, skipping work to unpack it.
         if (input_is_opaque) {
             c.a = p->splat(1.0f);