/**
- * 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,
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;