aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/core/SkHalf.h
blob: 2f2ed66c6a64016ee0ae512f92c2876298f2988e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/*
 * Copyright 2014 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SkHalf_DEFINED
#define SkHalf_DEFINED

#include "SkNx.h"
#include "SkTypes.h"

// 16-bit floating point value
// format is 1 bit sign, 5 bits exponent, 10 bits mantissa
// only used for storage
typedef uint16_t SkHalf;

#define SK_HalfMin      0x0400   // 2^-24  (minimum positive normal value)
#define SK_HalfMax      0x7bff   // 65504
#define SK_HalfEpsilon  0x1400   // 2^-10

// convert between half and single precision floating point
float SkHalfToFloat(SkHalf h);
SkHalf SkFloatToHalf(float f);

// Convert between half and single precision floating point,
// assuming inputs and outputs are both finite.
static inline     Sk4f SkHalfToFloat_finite(uint64_t);
static inline uint64_t SkFloatToHalf_finite(const Sk4f&);

// ~~~~~~~~~~~ impl ~~~~~~~~~~~~~~ //

// Like the serial versions in SkHalf.cpp, these are based on
// https://fgiesen.wordpress.com/2012/03/28/half-to-float-done-quic/

// GCC 4.9 lacks the intrinsics to use ARMv8 f16<->f32 instructions, so we use inline assembly.

static inline Sk4f SkHalfToFloat_finite(uint64_t hs) {
#if !defined(SKNX_NO_SIMD) && defined(SK_CPU_ARM64)
    float32x4_t fs;
    asm ("fmov  %d[fs], %[hs]        \n"   // vcreate_f16(hs)
         "fcvtl %[fs].4s, %[fs].4h   \n"   // vcvt_f32_f16(...)
        : [fs] "=w" (fs)                   // =w: write-only NEON register
        : [hs] "r" (hs));                  //  r: read-only 64-bit general register
    return fs;
#else
    Sk4i bits      = SkNx_cast<int>(Sk4h::Load(&hs)),   // Expand to 32 bit.
         sign      = bits & 0x00008000,                 // Save the sign bit for later...
         positive  = bits ^ sign,                       // ...but strip it off for now.
         is_denorm = positive < (1<<10);                // Exponent == 0?

    // For normal half floats, extend the mantissa by 13 zero bits,
    // then adjust the exponent from 15 bias to 127 bias.
    Sk4i norm = (positive << 13) + ((127 - 15) << 23);

    // For denorm half floats, mask in the exponent-only float K that turns our
    // denorm value V*2^-14 into a normalized float K + V*2^-14.  Then subtract off K.
    const Sk4i K = ((127-15) + (23-10) + 1) << 23;
    Sk4i mask_K = positive | K;
    Sk4f denorm = Sk4f::Load(&mask_K) - Sk4f::Load(&K);

    Sk4i merged = (sign << 16) | is_denorm.thenElse(Sk4i::Load(&denorm), norm);
    return Sk4f::Load(&merged);
#endif
}

static inline uint64_t SkFloatToHalf_finite(const Sk4f& fs) {
    uint64_t r;
#if !defined(SKNX_NO_SIMD) && defined(SK_CPU_ARM64)
    float32x4_t vec = fs.fVec;
    asm ("fcvtn %[vec].4h, %[vec].4s  \n"   // vcvt_f16_f32(vec)
         "fmov  %[r], %d[vec]         \n"   // vst1_f16(&r, ...)
        : [r] "=r" (r)                      // =r: write-only 64-bit general register
        , [vec] "+w" (vec));                // +w: read-write NEON register
#else
    Sk4i bits           = Sk4i::Load(&fs),
         sign           = bits & 0x80000000,              // Save the sign bit for later...
         positive       = bits ^ sign,                    // ...but strip it off for now.
         will_be_denorm = positive < ((127-15+1) << 23);  // positve < smallest normal half?

    // For normal half floats, adjust the exponent from 127 bias to 15 bias,
    // then drop the bottom 13 mantissa bits.
    Sk4i norm = (positive - ((127 - 15) << 23)) >> 13;

    // This mechanically inverts the denorm half -> normal float conversion above.
    // Knowning that and reading its explanation will leave you feeling more confident
    // than reading my best attempt at explaining this directly.
    const Sk4i K = ((127-15) + (23-10) + 1) << 23;
    Sk4f plus_K = Sk4f::Load(&positive) + Sk4f::Load(&K);
    Sk4i denorm = Sk4i::Load(&plus_K) ^ K;

    Sk4i merged = (sign >> 16) | will_be_denorm.thenElse(denorm, norm);
    SkNx_cast<uint16_t>(merged).store(&r);
#endif
    return r;
}

#endif