sketch Half in skvm

Key design points...

   A) Take care to not commit too much to Half's range or precision.
      In particular, don't offer operations whose result might not
      be representable in Half.  E.g., shy away from Half divide,
      not just because we might not be able to do a native divide,
      but even more so because division produces results that might
      not fit in Half, ±inf or NaN.

   B) No native Half loads, stores, uniforms or splats,
      instead converting from I32 or F32.  This keeps the entire
      front-end (user code, Builder, etc.) specified in terms of precise
      format and oblivious to the various backends' representations of
      Half.  Native Half splats would be less trouble than uniforms I
      think, and uniforms less trouble than loads and stores, but still
      enough a pain that we're better of deferring any of that for now.
      (Explicit fp16 uniforms do make sense to me though.)

   C) Keep the current F32-based Color and all the effect virtuals
      that use it around, introducing parallel Half-based HalfColors
      and entry points for those.  The key cool idea here is to have
      the default entry points for F32/Color and Half/HalfColor call
      each other, so that any given effect can implement one, the other,
      or both and always be compatble with however it's called.
      This is mostly about incremental rollout, but I suspect we'll
      have areas that stick to F32 forever.  (Think the IEEE 754 32-bit
      float specific bit hacks we used for approx_log/approx_exp.)

   D) (not done yet) allow implicit Half->F32 conversion, but not
      the other way around of course.  This makes it easier to lean
      on the body of F32 routines we already have, and again mostly
      helps enable incremental rollout.

Change-Id: I8bb38efbe476ff89dd2591411e115c2ab3757854
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/341800
Reviewed-by: Herb Derby <herb@google.com>
Commit-Queue: Mike Klein <mtklein@google.com>
This commit is contained in:
Mike Klein 2020-12-08 12:49:47 -06:00 committed by Skia Commit-Bot
parent b339bbe9dc
commit 9b0fc9c9c1
5 changed files with 196 additions and 3 deletions

View File

@ -81,6 +81,39 @@ skvm::Color SkColorFilterBase::program(skvm::Builder* p, skvm::Color c,
return {};
}
// This should look familiar... see just above.
skvm::HalfColor SkColorFilterBase::program(skvm::Builder* p, skvm::HalfColor c,
SkColorSpace* dstCS,
skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const {
skvm::Half original = c.a;
if ((c = this->onProgram(p,c, dstCS, uniforms,alloc))) {
if (this->isAlphaUnchanged()) {
c.a = original;
}
return c;
}
//SkDebugf("cannot onProgram %s\n", this->getTypeName());
return {};
}
skvm::Color SkColorFilterBase::onProgram(skvm::Builder* p, skvm::Color c,
SkColorSpace* dstCS,
skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const {
if (skvm::HalfColor hc = this->onProgram(p, to_Half(c), dstCS, uniforms, alloc)) {
return to_F32(hc);
}
return {};
}
skvm::HalfColor SkColorFilterBase::onProgram(skvm::Builder* p, skvm::HalfColor hc,
SkColorSpace* dstCS,
skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const {
if (skvm::Color c = this->onProgram(p, to_F32(hc), dstCS, uniforms, alloc)) {
return to_Half(c);
}
return {};
}
SkColor SkColorFilter::filterColor(SkColor c) const {
// This is mostly meaningless. We should phase-out this call entirely.
SkColorSpace* cs = nullptr;

View File

@ -29,6 +29,11 @@ public:
skvm::Color program(skvm::Builder*, skvm::Color,
SkColorSpace* dstCS, skvm::Uniforms*, SkArenaAlloc*) const;
SK_WARN_UNUSED_RESULT
skvm::HalfColor program(skvm::Builder*, skvm::HalfColor,
SkColorSpace* dstCS, skvm::Uniforms*, SkArenaAlloc*) const;
/** Returns the flags for this filter. Override in subclasses to return custom flags.
*/
virtual uint32_t onGetFlags() const { return 0; }
@ -78,8 +83,11 @@ protected:
private:
virtual bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const = 0;
virtual skvm::Color onProgram(skvm::Builder*, skvm::Color,
SkColorSpace* dstCS, skvm::Uniforms*, SkArenaAlloc*) const = 0;
// These defaults just call each other, so you must override at least one of them.
virtual skvm::Color onProgram(skvm::Builder*, skvm::Color,
SkColorSpace* dstCS, skvm::Uniforms*, SkArenaAlloc*) const;
virtual skvm::HalfColor onProgram(skvm::Builder*, skvm::HalfColor,
SkColorSpace* dstCS, skvm::Uniforms*, SkArenaAlloc*) const;
friend class SkColorFilter;

View File

@ -1079,6 +1079,11 @@ namespace skvm {
return round(mul(x, limit));
}
// Shhh... it's a secret, but Half is secretly F32 underneath for now!
// (This will definitely change. :P)
F32 Builder::to_F32 (Half x) { return {x.builder, x.id}; }
Half Builder::to_Half(F32 x) { return {x.builder, x.id}; }
bool SkColorType_to_PixelFormat(SkColorType ct, PixelFormat* f) {
auto UNORM = PixelFormat::UNORM,
FLOAT = PixelFormat::FLOAT;

View File

@ -472,6 +472,8 @@ namespace skvm {
struct Arg { int ix; };
// 32-bit signed integer (with both signed sra() and unsigned/logical shr() available).
// Think "int" or "int32_t".
struct I32 {
Builder* builder = nullptr;
Val id = NA;
@ -479,6 +481,7 @@ namespace skvm {
Builder* operator->() const { return builder; }
};
// 32-bit IEEE 754 float, think "float".
struct F32 {
Builder* builder = nullptr;
Val id = NA;
@ -486,8 +489,27 @@ namespace skvm {
Builder* operator->() const { return builder; }
};
// Comparisons of F32 or I32 return I32 masks, with false=0 and true=~0.
// An opaque float-y type with ambiguous precision and at least [-2,+2) range.
// This could be FP16, FP32, signed 1.14 fixed point, bfloat16, etc.
struct Half {
Builder* builder = nullptr;
Val id = NA;
explicit operator bool() const { return id != NA; }
Builder* operator->() const { return builder; }
};
// Integer mask returned by comparisons of Half, with false=0 and true=~0 as usual.
struct HalfMask {
Builder* builder = nullptr;
Val id = NA;
explicit operator bool() const { return id != NA; }
Builder* operator->() const { return builder; }
};
// Some operations make sense with immediate arguments,
// so we use I32a and F32a to receive them transparently.
// so we use I32a/F32a/Halfa to receive them transparently.
//
// We omit overloads that may indicate a bug or performance issue.
// In general it does not make sense to pass immediates to unary operations,
@ -517,6 +539,15 @@ namespace skvm {
float imm = 0;
};
struct Halfa {
Halfa(Half v) : SkDEBUGCODE(builder(v.builder),) id(v.id) {}
Halfa(float v) : imm(v) {}
SkDEBUGCODE(Builder* builder = nullptr;)
Val id = NA;
float imm = 0;
};
struct Color {
F32 r,g,b,a;
explicit operator bool() const { return r && g && b && a; }
@ -535,6 +566,12 @@ namespace skvm {
Builder* operator->() const { return x.operator->(); }
};
struct HalfColor {
Half r,g,b,a;
explicit operator bool() const { return r && g && b && a; }
Builder* operator->() const { return a.operator->(); }
};
struct Uniform {
Arg ptr;
int offset;
@ -623,6 +660,8 @@ namespace skvm {
void assert_true(I32 cond, F32 debug) { assert_true(cond, pun_to_I32(debug)); }
void assert_true(I32 cond) { assert_true(cond, cond); }
// TODO: Half asserts?
// Store {8,16,32,64,128}-bit varying.
void store8 (Arg ptr, I32 val);
void store16 (Arg ptr, I32 val);
@ -673,6 +712,7 @@ namespace skvm {
memcpy(&bits, &f, 4);
return pun_to_F32(splat(bits));
}
Half half(float f) { return to_Half(splat(f)); }
// float math, comparisons, etc.
F32 add(F32, F32); F32 add(F32a x, F32a y) { return add(_(x), _(y)); }
@ -731,6 +771,45 @@ namespace skvm {
I32 gt (F32, F32); I32 gt (F32a x, F32a y) { return gt (_(x), _(y)); }
I32 gte(F32, F32); I32 gte(F32a x, F32a y) { return gte(_(x), _(y)); }
// Half math, comparisons, etc.
Half add(Half, Half); Half add(Halfa x, Halfa y) { return add(_(x), _(y)); }
Half sub(Half, Half); Half sub(Halfa x, Halfa y) { return sub(_(x), _(y)); }
Half mul(Half, Half); Half mul(Halfa x, Halfa y) { return mul(_(x), _(y)); }
Half min(Half, Half); Half min(Halfa x, Halfa y) { return min(_(x), _(y)); }
Half max(Half, Half); Half max(Halfa x, Halfa y) { return max(_(x), _(y)); }
Half sqrt(Half);
Half abs(Half);
Half ceil(Half);
Half floor(Half);
Half fract(Half x) { return sub(x, floor(x)); }
Half lerp(Half lo, Half hi, Half t);
Half lerp(Halfa lo, Halfa hi, Halfa t) { return lerp(_(lo), _(hi), _(t)); }
Half clamp (Half x, Half lo, Half hi) { return max(lo, min(x, hi)); }
Half clamp (Halfa x, Halfa lo, Halfa hi) { return clamp(_(x), _(lo), _(hi)); }
Half clamp01(Half x) { return clamp(x, 0.0f, 1.0f); }
Half to_Half(F32 );
F32 to_F32 (Half);
HalfMask eq(Half, Half); HalfMask eq(Halfa x, Halfa y) { return eq(_(x), _(y)); }
HalfMask neq(Half, Half); HalfMask neq(Halfa x, Halfa y) { return neq(_(x), _(y)); }
HalfMask lt (Half, Half); HalfMask lt (Halfa x, Halfa y) { return lt (_(x), _(y)); }
HalfMask lte(Half, Half); HalfMask lte(Halfa x, Halfa y) { return lte(_(x), _(y)); }
HalfMask gt (Half, Half); HalfMask gt (Halfa x, Halfa y) { return gt (_(x), _(y)); }
HalfMask gte(Half, Half); HalfMask gte(Halfa x, Halfa y) { return gte(_(x), _(y)); }
HalfMask bit_and (HalfMask, HalfMask);
HalfMask bit_or (HalfMask, HalfMask);
HalfMask bit_xor (HalfMask, HalfMask);
HalfMask bit_clear(HalfMask, HalfMask);
Half select(HalfMask, Half, Half);
Half select(HalfMask cond, Halfa t, Halfa f) { return select(cond, _(t), _(f)); }
// int math, comparisons, etc.
I32 add(I32, I32); I32 add(I32a x, I32a y) { return add(_(x), _(y)); }
I32 sub(I32, I32); I32 sub(I32a x, I32a y) { return sub(_(x), _(y)); }
@ -829,6 +908,14 @@ namespace skvm {
return splat(x.imm);
}
Half _(Halfa x) {
if (x.id != NA) {
SkASSERT(x.builder == this);
return {this, x.id};
}
return half(x.imm);
}
bool allImm() const;
template <typename T, typename... Rest>
@ -998,6 +1085,44 @@ namespace skvm {
static inline F32& operator-=(F32& x, F32a y) { return (x = x - y); }
static inline F32& operator*=(F32& x, F32a y) { return (x = x * y); }
static inline Half operator+(Half x, Halfa y) { return x->add(x,y); }
static inline Half operator+(float x, Half y) { return y->add(x,y); }
static inline Half operator-(Half x, Halfa y) { return x->sub(x,y); }
static inline Half operator-(float x, Half y) { return y->sub(x,y); }
static inline Half operator*(Half x, Halfa y) { return x->mul(x,y); }
static inline Half operator*(float x, Half y) { return y->mul(x,y); }
static inline Half min(Half x, Halfa y) { return x->min(x,y); }
static inline Half min(float x, Half y) { return y->min(x,y); }
static inline Half max(Half x, Halfa y) { return x->max(x,y); }
static inline Half max(float x, Half y) { return y->max(x,y); }
static inline HalfMask operator==(Half x, Half y) { return x->eq(x,y); }
static inline HalfMask operator==(Half x, float y) { return x->eq(x,y); }
static inline HalfMask operator==(float x, Half y) { return y->eq(x,y); }
static inline HalfMask operator!=(Half x, Half y) { return x->neq(x,y); }
static inline HalfMask operator!=(Half x, float y) { return x->neq(x,y); }
static inline HalfMask operator!=(float x, Half y) { return y->neq(x,y); }
static inline HalfMask operator< (Half x, Halfa y) { return x->lt(x,y); }
static inline HalfMask operator< (float x, Half y) { return y->lt(x,y); }
static inline HalfMask operator<=(Half x, Halfa y) { return x->lte(x,y); }
static inline HalfMask operator<=(float x, Half y) { return y->lte(x,y); }
static inline HalfMask operator> (Half x, Halfa y) { return x->gt(x,y); }
static inline HalfMask operator> (float x, Half y) { return y->gt(x,y); }
static inline HalfMask operator>=(Half x, Halfa y) { return x->gte(x,y); }
static inline HalfMask operator>=(float x, Half y) { return y->gte(x,y); }
static inline Half to_Half(F32 x) { return x->to_Half(x); }
static inline F32 to_F32 (Half x) { return x->to_F32 (x); }
static inline void assert_true(I32 cond, I32 debug) { cond->assert_true(cond,debug); }
static inline void assert_true(I32 cond, F32 debug) { cond->assert_true(cond,debug); }
static inline void assert_true(I32 cond) { cond->assert_true(cond); }
@ -1129,6 +1254,25 @@ namespace skvm {
return poly(x, x*a+b, rest...);
}
}
static inline HalfColor to_Half(Color c) {
return {
c->to_Half(c.r),
c->to_Half(c.g),
c->to_Half(c.b),
c->to_Half(c.a),
};
}
static inline Color to_F32(HalfColor c) {
return {
c->to_F32(c.r),
c->to_F32(c.g),
c->to_F32(c.b),
c->to_F32(c.a),
};
}
} // namespace skvm
#endif//SkVM_DEFINED

View File

@ -15,6 +15,9 @@ namespace skvm {
struct Arg;
struct I32;
struct F32;
struct Half;
struct HalfMask;
struct HalfColor;
struct Color;
struct Coord;
struct Uniforms;