diff options
author | mtklein <mtklein@google.com> | 2016-07-14 12:03:04 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-07-14 12:03:04 -0700 |
commit | 64bbad360f30930befafee1bdefe4ad89f130dac (patch) | |
tree | 5e97d31dc9a0075de95cc2389427cb9138df466b /src/core/SkHalf.h | |
parent | 3296bee70d074bb8094b3229dbe12fa016657e90 (diff) |
Revert of Expand _01 half<->float limitation to _finite. Simplify. (patchset #7 id:120001 of https://codereview.chromium.org/2145663003/ )
Reason for revert:
Unit tests fail on Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Fast
Original issue's description:
> Expand _01 half<->float limitation to _finite. Simplify.
>
> It's become clear we need to sometimes deal with values <0 or >1.
> I'm not yet convinced we care about NaN or +-inf.
>
> We had some fairly clever tricks and optimizations here for NEON
> and SSE. I've thrown them out in favor of a single implementation.
> If we find the specializations mattered, we can certainly figure out
> how to extend them to this new range/domain.
>
> This happens to add a vectorized float -> half for ARMv7, which was
> missing from the _01 version. (The SSE strategy was not portable to
> platforms that flush denorm floats to zero.)
>
> I've tested the full float range for FloatToHalf on my desktop and a 5x.
>
> BUG=skia:
> GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2145663003
> CQ_INCLUDE_TRYBOTS=client.skia:Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-SKNX_NO_SIMD-Trybot;master.client.skia:Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-SKNX_NO_SIMD-Trybot,Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Fast-Trybot
>
> Committed: https://skia.googlesource.com/skia/+/3296bee70d074bb8094b3229dbe12fa016657e90
TBR=msarett@google.com,mtklein@chromium.org
# Skipping CQ checks because original CL landed less than 1 days ago.
NOPRESUBMIT=true
NOTREECHECKS=true
NOTRY=true
BUG=skia:
Review-Url: https://codereview.chromium.org/2151023003
Diffstat (limited to 'src/core/SkHalf.h')
-rw-r--r-- | src/core/SkHalf.h | 107 |
1 files changed, 66 insertions, 41 deletions
diff --git a/src/core/SkHalf.h b/src/core/SkHalf.h index 2f2ed66c6a..5f5575ae1a 100644 --- a/src/core/SkHalf.h +++ b/src/core/SkHalf.h @@ -24,10 +24,10 @@ typedef uint16_t SkHalf; 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&); +// Convert between half and single precision floating point, but pull any dirty +// trick we can to make it faster as long as it's correct enough for values in [0,1]. +static inline Sk4f SkHalfToFloat_01(uint64_t); +static inline uint64_t SkFloatToHalf_01(const Sk4f&); // ~~~~~~~~~~~ impl ~~~~~~~~~~~~~~ // @@ -36,7 +36,7 @@ static inline uint64_t SkFloatToHalf_finite(const Sk4f&); // 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) { +static inline Sk4f SkHalfToFloat_01(uint64_t hs) { #if !defined(SKNX_NO_SIMD) && defined(SK_CPU_ARM64) float32x4_t fs; asm ("fmov %d[fs], %[hs] \n" // vcreate_f16(hs) @@ -44,28 +44,53 @@ static inline Sk4f SkHalfToFloat_finite(uint64_t hs) { : [fs] "=w" (fs) // =w: write-only NEON register : [hs] "r" (hs)); // r: read-only 64-bit general register return fs; + +#elif !defined(SKNX_NO_SIMD) && defined(SK_ARM_HAS_NEON) + // NEON makes this pretty easy: + // - denormals are 10-bit * 2^-14 == 24-bit fixed point; + // - handle normals the same way as in SSE: align mantissa, then rebias exponent. + uint32x4_t h = vmovl_u16(vcreate_u16(hs)), + is_denorm = vcltq_u32(h, vdupq_n_u32(1<<10)); + float32x4_t denorm = vcvtq_n_f32_u32(h, 24), + norm = vreinterpretq_f32_u32(vaddq_u32(vshlq_n_u32(h, 13), + vdupq_n_u32((127-15) << 23))); + return vbslq_f32(is_denorm, denorm, norm); + +#elif !defined(SKNX_NO_SIMD) && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2 + // If our input is a normal 16-bit float, things are pretty easy: + // - shift left by 13 to put the mantissa in the right place; + // - the exponent is wrong, but it just needs to be rebiased; + // - re-bias the exponent from 15-bias to 127-bias by adding (127-15). + + // If our input is denormalized, we're going to do the same steps, plus a few more fix ups: + // - the input is h = K*2^-14, for some 10-bit fixed point K in [0,1); + // - by shifting left 13 and adding (127-15) to the exponent, we constructed the float value + // 2^-15*(1+K); + // - we'd need to subtract 2^-15 and multiply by 2 to get back to K*2^-14, or equivallently + // multiply by 2 then subtract 2^-14. + // + // - We'll work that multiply by 2 into the rebias, by adding 1 more to the exponent. + // - Conveniently, this leaves that rebias constant 2^-14, exactly what we want to subtract. + + __m128i h = _mm_unpacklo_epi16(_mm_loadl_epi64((const __m128i*)&hs), _mm_setzero_si128()); + const __m128i is_denorm = _mm_cmplt_epi32(h, _mm_set1_epi32(1<<10)); + + __m128i rebias = _mm_set1_epi32((127-15) << 23); + rebias = _mm_add_epi32(rebias, _mm_and_si128(is_denorm, _mm_set1_epi32(1<<23))); + + __m128i f = _mm_add_epi32(_mm_slli_epi32(h, 13), rebias); + return _mm_sub_ps(_mm_castsi128_ps(f), + _mm_castsi128_ps(_mm_and_si128(is_denorm, rebias))); #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); + float fs[4]; + for (int i = 0; i < 4; i++) { + fs[i] = SkHalfToFloat(hs >> (i*16)); + } + return Sk4f::Load(fs); #endif } -static inline uint64_t SkFloatToHalf_finite(const Sk4f& fs) { +static inline uint64_t SkFloatToHalf_01(const Sk4f& fs) { uint64_t r; #if !defined(SKNX_NO_SIMD) && defined(SK_CPU_ARM64) float32x4_t vec = fs.fVec; @@ -73,25 +98,25 @@ static inline uint64_t SkFloatToHalf_finite(const Sk4f& fs) { "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 + +// TODO: ARMv7 NEON float->half? + +#elif !defined(SKNX_NO_SIMD) && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2 + // Scale down from 127-bias to 15-bias, then cut off bottom 13 mantissa bits. + // This doesn't round, so it can be 1 bit too small. + const __m128 rebias = _mm_castsi128_ps(_mm_set1_epi32((127 - (127-15)) << 23)); + __m128i h = _mm_srli_epi32(_mm_castps_si128(_mm_mul_ps(fs.fVec, rebias)), 13); + _mm_storel_epi64((__m128i*)&r, _mm_packs_epi32(h,h)); + #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); + SkHalf hs[4]; + for (int i = 0; i < 4; i++) { + hs[i] = SkFloatToHalf(fs[i]); + } + r = (uint64_t)hs[3] << 48 + | (uint64_t)hs[2] << 32 + | (uint64_t)hs[1] << 16 + | (uint64_t)hs[0] << 0; #endif return r; } |