llvmpipe: Fix rendering to PIPE_FORMAT_R10G10B10A2_UNORM.
authorJosé Fonseca <jfonseca@vmware.com>
Fri, 20 Sep 2013 11:58:59 +0000 (12:58 +0100)
committerJosé Fonseca <jfonseca@vmware.com>
Fri, 20 Sep 2013 16:34:57 +0000 (17:34 +0100)
We must take rounding in consideration when re-scaling to narrow
normalized channels, such as 2-bit normalized alpha.

Reviewed-by: Roland Scheidegger <sroland@vmware.com>
src/gallium/drivers/llvmpipe/lp_state_fs.c

index 07e6685..875a3cf 100644 (file)
@@ -876,7 +876,22 @@ lp_blend_type_from_format_desc(const struct util_format_description *format_desc
 
 
 /**
- * Scale a normalized value from src_bits to dst_bits
+ * Scale a normalized value from src_bits to dst_bits.
+ *
+ * The exact calculation is
+ *
+ *    dst = iround(src * dst_mask / src_mask)
+ *
+ *  or with integer rounding
+ *
+ *    dst = src * (2*dst_mask + sign(src)*src_mask) / (2*src_mask)
+ *
+ *  where
+ *
+ *    src_mask = (1 << src_bits) - 1
+ *    dst_mask = (1 << dst_bits) - 1
+ *
+ * but we try to avoid division and multiplication through shifts.
  */
 static INLINE LLVMValueRef
 scale_bits(struct gallivm_state *gallivm,
@@ -889,11 +904,68 @@ scale_bits(struct gallivm_state *gallivm,
    LLVMValueRef result = src;
 
    if (dst_bits < src_bits) {
-      /* Scale down by LShr */
-      result = LLVMBuildLShr(builder,
-                             src,
-                             lp_build_const_int_vec(gallivm, src_type, src_bits - dst_bits),
-                             "");
+      int delta_bits = src_bits - dst_bits;
+
+      if (delta_bits <= dst_bits) {
+         /*
+          * Approximate the rescaling with a single shift.
+          *
+          * This gives the wrong rounding.
+          */
+
+         result = LLVMBuildLShr(builder,
+                                src,
+                                lp_build_const_int_vec(gallivm, src_type, delta_bits),
+                                "");
+
+      } else {
+         /*
+          * Try more accurate rescaling.
+          */
+
+         /*
+          * Drop the least significant bits to make space for the multiplication.
+          *
+          * XXX: A better approach would be to use a wider integer type as intermediate.  But
+          * this is enough to convert alpha from 16bits -> 2 when rendering to
+          * PIPE_FORMAT_R10G10B10A2_UNORM.
+          */
+         result = LLVMBuildLShr(builder,
+                                src,
+                                lp_build_const_int_vec(gallivm, src_type, dst_bits),
+                                "");
+
+
+         result = LLVMBuildMul(builder,
+                               result,
+                               lp_build_const_int_vec(gallivm, src_type, (1LL << dst_bits) - 1),
+                               "");
+
+         /*
+          * Add a rounding term before the division.
+          *
+          * TODO: Handle signed integers too.
+          */
+         if (!src_type.sign) {
+            result = LLVMBuildAdd(builder,
+                                  result,
+                                  lp_build_const_int_vec(gallivm, src_type, (1LL << (delta_bits - 1))),
+                                  "");
+         }
+
+         /*
+          * Approximate the division by src_mask with a src_bits shift.
+          *
+          * Given the src has already been shifted by dst_bits, all we need
+          * to do is to shift by the difference.
+          */
+
+         result = LLVMBuildLShr(builder,
+                                result,
+                                lp_build_const_int_vec(gallivm, src_type, delta_bits),
+                                "");
+      }
+
    } else if (dst_bits > src_bits) {
       /* Scale up bits */
       int db = dst_bits - src_bits;