skia2/tests/Float16Test.cpp
mtklein 8ae991e433 Flush denorm half floats to zero.
I think we convinced ourselves that denorms, while a good chunk of half floats,
cover a rather small fraction of the representable range, which is always
close enough to zero to flush.

This makes both paths of the conversion to or from float considerably simpler.

These functions now work for zero-or-normal half floats (excluding infinite, NaN).
I'm not aware of a term for this class so I've called them "ordinary".

A handful of GMs and SKPs draw differently in --config f16, but all imperceptibly.

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2256023002

Review-Url: https://codereview.chromium.org/2256023002
2016-08-22 13:20:18 -07:00

107 lines
3.0 KiB
C++

/*
* Copyright 2016 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "Test.h"
#include "SkAutoPixmapStorage.h"
#include "SkColor.h"
#include "SkHalf.h"
#include "SkOpts.h"
#include "SkPixmap.h"
#include "SkPM4f.h"
#include "SkRandom.h"
static bool eq_within_half_float(float a, float b) {
const float kTolerance = 1.0f / (1 << (8 + 10));
SkHalf ha = SkFloatToHalf(a);
SkHalf hb = SkFloatToHalf(b);
float a2 = SkHalfToFloat(ha);
float b2 = SkHalfToFloat(hb);
return fabsf(a2 - b2) <= kTolerance;
}
static bool eq_within_half_float(const SkPM4f& a, const SkPM4f& b) {
for (int i = 0; i < 4; ++i) {
if (!eq_within_half_float(a.fVec[i], b.fVec[i])) {
return false;
}
}
return true;
}
DEF_TEST(color_half_float, reporter) {
const int w = 100;
const int h = 100;
SkImageInfo info = SkImageInfo::Make(w, h, kRGBA_F16_SkColorType, kPremul_SkAlphaType);
SkAutoPixmapStorage pm;
pm.alloc(info);
REPORTER_ASSERT(reporter, pm.getSafeSize() == SkToSizeT(w * h * sizeof(uint64_t)));
SkColor4f c4 { 1, 0.5f, 0.25f, 0.5f };
pm.erase(c4);
SkPM4f origpm4 = c4.premul();
for (int y = 0; y < pm.height(); ++y) {
for (int x = 0; x < pm.width(); ++x) {
SkPM4f pm4 = SkPM4f::FromF16(pm.addrF16(x, y));
REPORTER_ASSERT(reporter, eq_within_half_float(origpm4, pm4));
}
}
}
static bool is_denorm(uint16_t h) {
return (h & 0x7fff) < 0x0400;
}
static bool is_finite(uint16_t h) {
return (h & 0x7c00) != 0x7c00;
}
DEF_TEST(SkHalfToFloat_finite_ftz, r) {
for (uint32_t h = 0; h <= 0xffff; h++) {
if (!is_finite(h)) {
// _finite_ftz() only works for values that can be represented as a finite half float.
continue;
}
// _finite_ftz() flushes denorms to zero. 0.0f will compare == with both +0.0f and -0.0f.
float expected = is_denorm(h) ? 0.0f : SkHalfToFloat(h);
REPORTER_ASSERT(r, SkHalfToFloat_finite_ftz(h)[0] == expected);
}
}
DEF_TEST(SkFloatToHalf_finite_ftz, r) {
#if 0
for (uint64_t bits = 0; bits <= 0xffffffff; bits++) {
#else
SkRandom rand;
for (int i = 0; i < 1000000; i++) {
uint32_t bits = rand.nextU();
#endif
float f;
memcpy(&f, &bits, 4);
uint16_t expected = SkFloatToHalf(f);
if (!is_finite(expected)) {
// _finite_ftz() only works for values that can be represented as a finite half float.
continue;
}
if (is_denorm(expected)) {
// _finite_ftz() flushes denorms to zero, and happens to keep the sign bit.
expected = signbit(f) ? 0x8000 : 0x0000;
}
uint16_t actual = SkFloatToHalf_finite_ftz(Sk4f{f})[0];
// _finite_ftz() truncates instead of rounding, so it may be one too small.
REPORTER_ASSERT(r, actual == expected || actual == expected - 1);
}
}