• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2014 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #ifndef SkHalf_DEFINED
9 #define SkHalf_DEFINED
10 
11 #include "SkNx.h"
12 #include "SkTypes.h"
13 
14 // 16-bit floating point value
15 // format is 1 bit sign, 5 bits exponent, 10 bits mantissa
16 // only used for storage
17 typedef uint16_t SkHalf;
18 
19 #define SK_HalfMin      0x0400   // 2^-24  (minimum positive normal value)
20 #define SK_HalfMax      0x7bff   // 65504
21 #define SK_HalfEpsilon  0x1400   // 2^-10
22 
23 // convert between half and single precision floating point
24 float SkHalfToFloat(SkHalf h);
25 SkHalf SkFloatToHalf(float f);
26 
27 // Convert between half and single precision floating point, but pull any dirty
28 // trick we can to make it faster as long as it's correct enough for values in [0,1].
29 static inline     Sk4f SkHalfToFloat_01(uint64_t);
30 static inline uint64_t SkFloatToHalf_01(const Sk4f&);
31 
32 // ~~~~~~~~~~~ impl ~~~~~~~~~~~~~~ //
33 
34 // Like the serial versions in SkHalf.cpp, these are based on
35 // https://fgiesen.wordpress.com/2012/03/28/half-to-float-done-quic/
36 
37 // GCC 4.9 lacks the intrinsics to use ARMv8 f16<->f32 instructions, so we use inline assembly.
38 
SkHalfToFloat_01(uint64_t hs)39 static inline Sk4f SkHalfToFloat_01(uint64_t hs) {
40 #if !defined(SKNX_NO_SIMD) && defined(SK_CPU_ARM64)
41     float32x4_t fs;
42     asm ("fmov  %d[fs], %[hs]        \n"   // vcreate_f16(hs)
43          "fcvtl %[fs].4s, %[fs].4h   \n"   // vcvt_f32_f16(...)
44         : [fs] "=w" (fs)                   // =w: write-only NEON register
45         : [hs] "r" (hs));                  //  r: read-only 64-bit general register
46     return fs;
47 
48 #elif !defined(SKNX_NO_SIMD) && defined(SK_ARM_HAS_NEON)
49     // NEON makes this pretty easy:
50     //   - denormals are 10-bit * 2^-14 == 24-bit fixed point;
51     //   - handle normals the same way as in SSE: align mantissa, then rebias exponent.
52     uint32x4_t h = vmovl_u16(vcreate_u16(hs)),
53                is_denorm = vcltq_u32(h, vdupq_n_u32(1<<10));
54     float32x4_t denorm = vcvtq_n_f32_u32(h, 24),
55                   norm = vreinterpretq_f32_u32(vaddq_u32(vshlq_n_u32(h, 13),
56                                                          vdupq_n_u32((127-15) << 23)));
57     return vbslq_f32(is_denorm, denorm, norm);
58 
59 #elif !defined(SKNX_NO_SIMD) && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2
60     // If our input is a normal 16-bit float, things are pretty easy:
61     //   - shift left by 13 to put the mantissa in the right place;
62     //   - the exponent is wrong, but it just needs to be rebiased;
63     //   - re-bias the exponent from 15-bias to 127-bias by adding (127-15).
64 
65     // If our input is denormalized, we're going to do the same steps, plus a few more fix ups:
66     //   - the input is h = K*2^-14, for some 10-bit fixed point K in [0,1);
67     //   - by shifting left 13 and adding (127-15) to the exponent, we constructed the float value
68     //     2^-15*(1+K);
69     //   - we'd need to subtract 2^-15 and multiply by 2 to get back to K*2^-14, or equivallently
70     //     multiply by 2 then subtract 2^-14.
71     //
72     //   - We'll work that multiply by 2 into the rebias, by adding 1 more to the exponent.
73     //   - Conveniently, this leaves that rebias constant 2^-14, exactly what we want to subtract.
74 
75     __m128i h = _mm_unpacklo_epi16(_mm_loadl_epi64((const __m128i*)&hs), _mm_setzero_si128());
76     const __m128i is_denorm = _mm_cmplt_epi32(h, _mm_set1_epi32(1<<10));
77 
78     __m128i rebias = _mm_set1_epi32((127-15) << 23);
79     rebias = _mm_add_epi32(rebias, _mm_and_si128(is_denorm, _mm_set1_epi32(1<<23)));
80 
81     __m128i f = _mm_add_epi32(_mm_slli_epi32(h, 13), rebias);
82     return _mm_sub_ps(_mm_castsi128_ps(f),
83                       _mm_castsi128_ps(_mm_and_si128(is_denorm, rebias)));
84 #else
85     float fs[4];
86     for (int i = 0; i < 4; i++) {
87         fs[i] = SkHalfToFloat(hs >> (i*16));
88     }
89     return Sk4f::Load(fs);
90 #endif
91 }
92 
SkFloatToHalf_01(const Sk4f & fs)93 static inline uint64_t SkFloatToHalf_01(const Sk4f& fs) {
94     uint64_t r;
95 #if !defined(SKNX_NO_SIMD) && defined(SK_CPU_ARM64)
96     float32x4_t vec = fs.fVec;
97     asm ("fcvtn %[vec].4h, %[vec].4s  \n"   // vcvt_f16_f32(vec)
98          "fmov  %[r], %d[vec]         \n"   // vst1_f16(&r, ...)
99         : [r] "=r" (r)                      // =r: write-only 64-bit general register
100         , [vec] "+w" (vec));                // +w: read-write NEON register
101 
102 // TODO: ARMv7 NEON float->half?
103 
104 #elif !defined(SKNX_NO_SIMD) && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2
105     // Scale down from 127-bias to 15-bias, then cut off bottom 13 mantissa bits.
106     // This doesn't round, so it can be 1 bit too small.
107     const __m128 rebias = _mm_castsi128_ps(_mm_set1_epi32((127 - (127-15)) << 23));
108     __m128i h = _mm_srli_epi32(_mm_castps_si128(_mm_mul_ps(fs.fVec, rebias)), 13);
109     _mm_storel_epi64((__m128i*)&r, _mm_packs_epi32(h,h));
110 
111 #else
112     SkHalf hs[4];
113     for (int i = 0; i < 4; i++) {
114         hs[i] = SkFloatToHalf(fs[i]);
115     }
116     r = (uint64_t)hs[3] << 48
117       | (uint64_t)hs[2] << 32
118       | (uint64_t)hs[1] << 16
119       | (uint64_t)hs[0] <<  0;
120 #endif
121     return r;
122 }
123 
124 #endif
125