simpler immediates

That last patch was too ambitious: the existing logic is error-prone,
and to fix it would require implementing a whole side algebra for
immediates.  I don't really want to have a second side system, even if
we work out how to make it correct.

If instead we just require a few more splats, we can come up with a
simple sane system that at least can't blow up on you unexpectedly.

 - Strip F32/I32 back down to a Builder pointer and Val ID,
   but create F32a/I32a for use as method arguments that
   can auto-construct from an immediate or the normal type.

 - Add method overloads using F32a/I32a.

 - Add operators using F32a/I32a and int/float primitive types,
   guaranteeing there's at least one F32/I32 to get a Builder from.

 - To keep things simple, drop all the operator forward declarations.
   They're only needed for inline methods on Builder, and there
   are few enough of them that calling methods is fine.

 - TODO: some of the inline helpers become simple enough
   when written with operators they might not need to exist,
   e.g.  inv(x) -> (1-x).

Change-Id: I193b0e5bca5617ab564c769b916473434ad6d56d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/280276
Reviewed-by: Herb Derby <herb@google.com>
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Mike Klein <mtklein@google.com>
This commit is contained in:
Mike Klein 2020-03-29 12:03:49 -05:00
parent e9bc857b39
commit 46de36f993
3 changed files with 311 additions and 327 deletions

View File

@ -722,22 +722,6 @@ namespace skvm {
return id;
}
Val I32::resolve(Builder* b) {
if (!fBuilder) {
*this = b->splat(fImm);
}
SkASSERT(fBuilder == b);
return fID;
}
Val F32::resolve(Builder* b) {
if (!fBuilder) {
*this = b->splat(fImm);
}
SkASSERT(fBuilder == b);
return fID;
}
bool Builder::allImm() const { return true; }
template <typename T, typename... Rest>
@ -759,14 +743,14 @@ namespace skvm {
void Builder::assert_true(I32 cond, I32 debug) {
#ifdef SK_DEBUG
int imm;
if (this->allImm(id(cond),&imm)) { SkASSERT(imm); return; }
(void)push(Op::assert_true, id(cond),id(debug),NA);
if (this->allImm(cond.id,&imm)) { SkASSERT(imm); return; }
(void)push(Op::assert_true, cond.id,debug.id,NA);
#endif
}
void Builder::store8 (Arg ptr, I32 val) { (void)push(Op::store8 , id(val),NA,NA, ptr.ix); }
void Builder::store16(Arg ptr, I32 val) { (void)push(Op::store16, id(val),NA,NA, ptr.ix); }
void Builder::store32(Arg ptr, I32 val) { (void)push(Op::store32, id(val),NA,NA, ptr.ix); }
void Builder::store8 (Arg ptr, I32 val) { (void)push(Op::store8 , val.id,NA,NA, ptr.ix); }
void Builder::store16(Arg ptr, I32 val) { (void)push(Op::store16, val.id,NA,NA, ptr.ix); }
void Builder::store32(Arg ptr, I32 val) { (void)push(Op::store32, val.id,NA,NA, ptr.ix); }
I32 Builder::index() { return {this, push(Op::index , NA,NA,NA,0) }; }
@ -775,13 +759,13 @@ namespace skvm {
I32 Builder::load32(Arg ptr) { return {this, push(Op::load32, NA,NA,NA, ptr.ix) }; }
I32 Builder::gather8 (Arg ptr, int offset, I32 index) {
return {this, push(Op::gather8 , id(index),NA,NA, ptr.ix,offset)};
return {this, push(Op::gather8 , index.id,NA,NA, ptr.ix,offset)};
}
I32 Builder::gather16(Arg ptr, int offset, I32 index) {
return {this, push(Op::gather16, id(index),NA,NA, ptr.ix,offset)};
return {this, push(Op::gather16, index.id,NA,NA, ptr.ix,offset)};
}
I32 Builder::gather32(Arg ptr, int offset, I32 index) {
return {this, push(Op::gather32, id(index),NA,NA, ptr.ix,offset)};
return {this, push(Op::gather32, index.id,NA,NA, ptr.ix,offset)};
}
I32 Builder::uniform8(Arg ptr, int offset) {
@ -831,55 +815,55 @@ namespace skvm {
F32 Builder::add(F32 x, F32 y) {
float X,Y;
if (this->allImm(id(x),&X, id(y),&Y)) { return this->splat(X+Y); }
if (this->isImm(id(y), 0.0f)) { return x; } // x+0 == x
if (this->isImm(id(x), 0.0f)) { return y; } // 0+y == y
if (this->allImm(x.id,&X, y.id,&Y)) { return this->splat(X+Y); }
if (this->isImm(y.id, 0.0f)) { return x; } // x+0 == x
if (this->isImm(x.id, 0.0f)) { return y; } // 0+y == y
if (fma_supported()) {
if (fProgram[id(x)].op == Op::mul_f32) {
return {this, push(Op::fma_f32, fProgram[id(x)].x, fProgram[id(x)].y, id(y))};
if (fProgram[x.id].op == Op::mul_f32) {
return {this, push(Op::fma_f32, fProgram[x.id].x, fProgram[x.id].y, y.id)};
}
if (fProgram[id(y)].op == Op::mul_f32) {
return {this, push(Op::fma_f32, fProgram[id(y)].x, fProgram[id(y)].y, id(x))};
if (fProgram[y.id].op == Op::mul_f32) {
return {this, push(Op::fma_f32, fProgram[y.id].x, fProgram[y.id].y, x.id)};
}
}
return {this, push(Op::add_f32, id(x), id(y))};
return {this, push(Op::add_f32, x.id, y.id)};
}
F32 Builder::sub(F32 x, F32 y) {
float X,Y;
if (this->allImm(id(x),&X, id(y),&Y)) { return this->splat(X-Y); }
if (this->isImm(id(y), 0.0f)) { return x; } // x-0 == x
if (this->allImm(x.id,&X, y.id,&Y)) { return this->splat(X-Y); }
if (this->isImm(y.id, 0.0f)) { return x; } // x-0 == x
if (fma_supported()) {
if (fProgram[id(x)].op == Op::mul_f32) {
return {this, push(Op::fms_f32, fProgram[id(x)].x, fProgram[id(x)].y, id(y))};
if (fProgram[x.id].op == Op::mul_f32) {
return {this, push(Op::fms_f32, fProgram[x.id].x, fProgram[x.id].y, y.id)};
}
if (fProgram[id(y)].op == Op::mul_f32) {
return {this, push(Op::fnma_f32, fProgram[id(y)].x, fProgram[id(y)].y, id(x))};
if (fProgram[y.id].op == Op::mul_f32) {
return {this, push(Op::fnma_f32, fProgram[y.id].x, fProgram[y.id].y, x.id)};
}
}
return {this, push(Op::sub_f32, id(x), id(y))};
return {this, push(Op::sub_f32, x.id, y.id)};
}
F32 Builder::mul(F32 x, F32 y) {
float X,Y;
if (this->allImm(id(x),&X, id(y),&Y)) { return this->splat(X*Y); }
if (this->isImm(id(y), 1.0f)) { return x; } // x*1 == x
if (this->isImm(id(x), 1.0f)) { return y; } // 1*y == y
return {this, push(Op::mul_f32, id(x), id(y))};
if (this->allImm(x.id,&X, y.id,&Y)) { return this->splat(X*Y); }
if (this->isImm(y.id, 1.0f)) { return x; } // x*1 == x
if (this->isImm(x.id, 1.0f)) { return y; } // 1*y == y
return {this, push(Op::mul_f32, x.id, y.id)};
}
F32 Builder::div(F32 x, F32 y) {
float X,Y;
if (this->allImm(id(x),&X, id(y),&Y)) { return this->splat(X/Y); }
if (this->isImm(id(y), 1.0f)) { return x; } // x/1 == x
return {this, push(Op::div_f32, id(x), id(y))};
if (this->allImm(x.id,&X, y.id,&Y)) { return this->splat(X/Y); }
if (this->isImm(y.id, 1.0f)) { return x; } // x/1 == x
return {this, push(Op::div_f32, x.id, y.id)};
}
F32 Builder::sqrt(F32 x) {
float X;
if (this->allImm(id(x),&X)) { return this->splat(std::sqrt(X)); }
return {this, push(Op::sqrt_f32, id(x),NA,NA)};
if (this->allImm(x.id,&X)) { return this->splat(std::sqrt(X)); }
return {this, push(Op::sqrt_f32, x.id,NA,NA)};
}
// See http://www.machinedlearnings.com/2011/06/fast-approximate-logarithm-exponential.html.
@ -914,167 +898,167 @@ namespace skvm {
F32 Builder::min(F32 x, F32 y) {
float X,Y;
if (this->allImm(id(x),&X, id(y),&Y)) { return this->splat(std::min(X,Y)); }
return {this, push(Op::min_f32, id(x), id(y))};
if (this->allImm(x.id,&X, y.id,&Y)) { return this->splat(std::min(X,Y)); }
return {this, push(Op::min_f32, x.id, y.id)};
}
F32 Builder::max(F32 x, F32 y) {
float X,Y;
if (this->allImm(id(x),&X, id(y),&Y)) { return this->splat(std::max(X,Y)); }
return {this, push(Op::max_f32, id(x), id(y))};
if (this->allImm(x.id,&X, y.id,&Y)) { return this->splat(std::max(X,Y)); }
return {this, push(Op::max_f32, x.id, y.id)};
}
I32 Builder::add(I32 x, I32 y) { return {this, push(Op::add_i32, id(x), id(y))}; }
I32 Builder::sub(I32 x, I32 y) { return {this, push(Op::sub_i32, id(x), id(y))}; }
I32 Builder::mul(I32 x, I32 y) { return {this, push(Op::mul_i32, id(x), id(y))}; }
I32 Builder::add(I32 x, I32 y) { return {this, push(Op::add_i32, x.id, y.id)}; }
I32 Builder::sub(I32 x, I32 y) { return {this, push(Op::sub_i32, x.id, y.id)}; }
I32 Builder::mul(I32 x, I32 y) { return {this, push(Op::mul_i32, x.id, y.id)}; }
I32 Builder::add_16x2(I32 x, I32 y) { return {this, push(Op::add_i16x2, id(x), id(y))}; }
I32 Builder::sub_16x2(I32 x, I32 y) { return {this, push(Op::sub_i16x2, id(x), id(y))}; }
I32 Builder::mul_16x2(I32 x, I32 y) { return {this, push(Op::mul_i16x2, id(x), id(y))}; }
I32 Builder::add_16x2(I32 x, I32 y) { return {this, push(Op::add_i16x2, x.id, y.id)}; }
I32 Builder::sub_16x2(I32 x, I32 y) { return {this, push(Op::sub_i16x2, x.id, y.id)}; }
I32 Builder::mul_16x2(I32 x, I32 y) { return {this, push(Op::mul_i16x2, x.id, y.id)}; }
I32 Builder::shl(I32 x, int bits) {
if (bits == 0) { return x; }
int X;
if (this->allImm(id(x),&X)) { return this->splat(X << bits); }
return {this, push(Op::shl_i32, id(x),NA,NA, bits)};
if (this->allImm(x.id,&X)) { return this->splat(X << bits); }
return {this, push(Op::shl_i32, x.id,NA,NA, bits)};
}
I32 Builder::shr(I32 x, int bits) {
if (bits == 0) { return x; }
int X;
if (this->allImm(id(x),&X)) { return this->splat(unsigned(X) >> bits); }
return {this, push(Op::shr_i32, id(x),NA,NA, bits)};
if (this->allImm(x.id,&X)) { return this->splat(unsigned(X) >> bits); }
return {this, push(Op::shr_i32, x.id,NA,NA, bits)};
}
I32 Builder::sra(I32 x, int bits) {
if (bits == 0) { return x; }
int X;
if (this->allImm(id(x),&X)) { return this->splat(X >> bits); }
return {this, push(Op::sra_i32, id(x),NA,NA, bits)};
if (this->allImm(x.id,&X)) { return this->splat(X >> bits); }
return {this, push(Op::sra_i32, x.id,NA,NA, bits)};
}
I32 Builder::shl_16x2(I32 x, int k) { return {this, push(Op::shl_i16x2, id(x),NA,NA, k)}; }
I32 Builder::shr_16x2(I32 x, int k) { return {this, push(Op::shr_i16x2, id(x),NA,NA, k)}; }
I32 Builder::sra_16x2(I32 x, int k) { return {this, push(Op::sra_i16x2, id(x),NA,NA, k)}; }
I32 Builder::shl_16x2(I32 x, int k) { return {this, push(Op::shl_i16x2, x.id,NA,NA, k)}; }
I32 Builder::shr_16x2(I32 x, int k) { return {this, push(Op::shr_i16x2, x.id,NA,NA, k)}; }
I32 Builder::sra_16x2(I32 x, int k) { return {this, push(Op::sra_i16x2, x.id,NA,NA, k)}; }
I32 Builder:: eq(F32 x, F32 y) {
float X,Y;
if (this->allImm(id(x),&X, id(y),&Y)) { return this->splat(X==Y ? ~0 : 0); }
return {this, push(Op::eq_f32, id(x), id(y))};
if (this->allImm(x.id,&X, y.id,&Y)) { return this->splat(X==Y ? ~0 : 0); }
return {this, push(Op::eq_f32, x.id, y.id)};
}
I32 Builder::neq(F32 x, F32 y) {
float X,Y;
if (this->allImm(id(x),&X, id(y),&Y)) { return this->splat(X!=Y ? ~0 : 0); }
return {this, push(Op::neq_f32, id(x), id(y))};
if (this->allImm(x.id,&X, y.id,&Y)) { return this->splat(X!=Y ? ~0 : 0); }
return {this, push(Op::neq_f32, x.id, y.id)};
}
I32 Builder::lt(F32 x, F32 y) {
float X,Y;
if (this->allImm(id(x),&X, id(y),&Y)) { return this->splat(Y> X ? ~0 : 0); }
return {this, push(Op::gt_f32, id(y), id(x))};
if (this->allImm(x.id,&X, y.id,&Y)) { return this->splat(Y> X ? ~0 : 0); }
return {this, push(Op::gt_f32, y.id, x.id)};
}
I32 Builder::lte(F32 x, F32 y) {
float X,Y;
if (this->allImm(id(x),&X, id(y),&Y)) { return this->splat(Y>=X ? ~0 : 0); }
return {this, push(Op::gte_f32, id(y), id(x))};
if (this->allImm(x.id,&X, y.id,&Y)) { return this->splat(Y>=X ? ~0 : 0); }
return {this, push(Op::gte_f32, y.id, x.id)};
}
I32 Builder::gt(F32 x, F32 y) {
float X,Y;
if (this->allImm(id(x),&X, id(y),&Y)) { return this->splat(X> Y ? ~0 : 0); }
return {this, push(Op::gt_f32, id(x), id(y))};
if (this->allImm(x.id,&X, y.id,&Y)) { return this->splat(X> Y ? ~0 : 0); }
return {this, push(Op::gt_f32, x.id, y.id)};
}
I32 Builder::gte(F32 x, F32 y) {
float X,Y;
if (this->allImm(id(x),&X, id(y),&Y)) { return this->splat(X>=Y ? ~0 : 0); }
return {this, push(Op::gte_f32, id(x), id(y))};
if (this->allImm(x.id,&X, y.id,&Y)) { return this->splat(X>=Y ? ~0 : 0); }
return {this, push(Op::gte_f32, x.id, y.id)};
}
I32 Builder:: eq(I32 x, I32 y) { return {this, push(Op:: eq_i32, id(x), id(y))}; }
I32 Builder::neq(I32 x, I32 y) { return {this, push(Op::neq_i32, id(x), id(y))}; }
I32 Builder:: lt(I32 x, I32 y) { return {this, push(Op:: gt_i32, id(y), id(x))}; }
I32 Builder::lte(I32 x, I32 y) { return {this, push(Op::gte_i32, id(y), id(x))}; }
I32 Builder:: gt(I32 x, I32 y) { return {this, push(Op:: gt_i32, id(x), id(y))}; }
I32 Builder::gte(I32 x, I32 y) { return {this, push(Op::gte_i32, id(x), id(y))}; }
I32 Builder:: eq(I32 x, I32 y) { return {this, push(Op:: eq_i32, x.id, y.id)}; }
I32 Builder::neq(I32 x, I32 y) { return {this, push(Op::neq_i32, x.id, y.id)}; }
I32 Builder:: lt(I32 x, I32 y) { return {this, push(Op:: gt_i32, y.id, x.id)}; }
I32 Builder::lte(I32 x, I32 y) { return {this, push(Op::gte_i32, y.id, x.id)}; }
I32 Builder:: gt(I32 x, I32 y) { return {this, push(Op:: gt_i32, x.id, y.id)}; }
I32 Builder::gte(I32 x, I32 y) { return {this, push(Op::gte_i32, x.id, y.id)}; }
I32 Builder:: eq_16x2(I32 x, I32 y) { return {this, push(Op:: eq_i16x2, id(x), id(y))}; }
I32 Builder::neq_16x2(I32 x, I32 y) { return {this, push(Op::neq_i16x2, id(x), id(y))}; }
I32 Builder:: lt_16x2(I32 x, I32 y) { return {this, push(Op:: gt_i16x2, id(y), id(x))}; }
I32 Builder::lte_16x2(I32 x, I32 y) { return {this, push(Op::gte_i16x2, id(y), id(x))}; }
I32 Builder:: gt_16x2(I32 x, I32 y) { return {this, push(Op:: gt_i16x2, id(x), id(y))}; }
I32 Builder::gte_16x2(I32 x, I32 y) { return {this, push(Op::gte_i16x2, id(x), id(y))}; }
I32 Builder:: eq_16x2(I32 x, I32 y) { return {this, push(Op:: eq_i16x2, x.id, y.id)}; }
I32 Builder::neq_16x2(I32 x, I32 y) { return {this, push(Op::neq_i16x2, x.id, y.id)}; }
I32 Builder:: lt_16x2(I32 x, I32 y) { return {this, push(Op:: gt_i16x2, y.id, x.id)}; }
I32 Builder::lte_16x2(I32 x, I32 y) { return {this, push(Op::gte_i16x2, y.id, x.id)}; }
I32 Builder:: gt_16x2(I32 x, I32 y) { return {this, push(Op:: gt_i16x2, x.id, y.id)}; }
I32 Builder::gte_16x2(I32 x, I32 y) { return {this, push(Op::gte_i16x2, x.id, y.id)}; }
I32 Builder::bit_and(I32 x, I32 y) {
int X,Y;
if (this->allImm(id(x),&X, id(y),&Y)) { return this->splat(X&Y); }
if (this->isImm(id(y), 0)) { return this->splat(0); } // (x & false) == false
if (this->isImm(id(x), 0)) { return this->splat(0); } // (false & y) == false
if (this->isImm(id(y),~0)) { return x; } // (x & true) == x
if (this->isImm(id(x),~0)) { return y; } // (true & y) == y
return {this, push(Op::bit_and, id(x), id(y))};
if (this->allImm(x.id,&X, y.id,&Y)) { return this->splat(X&Y); }
if (this->isImm(y.id, 0)) { return this->splat(0); } // (x & false) == false
if (this->isImm(x.id, 0)) { return this->splat(0); } // (false & y) == false
if (this->isImm(y.id,~0)) { return x; } // (x & true) == x
if (this->isImm(x.id,~0)) { return y; } // (true & y) == y
return {this, push(Op::bit_and, x.id, y.id)};
}
I32 Builder::bit_or(I32 x, I32 y) {
int X,Y;
if (this->allImm(id(x),&X, id(y),&Y)) { return this->splat(X|Y); }
if (this->isImm(id(y), 0)) { return x; } // (x | false) == x
if (this->isImm(id(x), 0)) { return y; } // (false | y) == y
if (this->isImm(id(y),~0)) { return this->splat(~0); } // (x | true) == true
if (this->isImm(id(x),~0)) { return this->splat(~0); } // (true | y) == true
return {this, push(Op::bit_or, id(x), id(y))};
if (this->allImm(x.id,&X, y.id,&Y)) { return this->splat(X|Y); }
if (this->isImm(y.id, 0)) { return x; } // (x | false) == x
if (this->isImm(x.id, 0)) { return y; } // (false | y) == y
if (this->isImm(y.id,~0)) { return this->splat(~0); } // (x | true) == true
if (this->isImm(x.id,~0)) { return this->splat(~0); } // (true | y) == true
return {this, push(Op::bit_or, x.id, y.id)};
}
I32 Builder::bit_xor(I32 x, I32 y) {
int X,Y;
if (this->allImm(id(x),&X, id(y),&Y)) { return this->splat(X^Y); }
if (this->isImm(id(y), 0)) { return x; } // (x ^ false) == x
if (this->isImm(id(x), 0)) { return y; } // (false ^ y) == y
return {this, push(Op::bit_xor, id(x), id(y))};
if (this->allImm(x.id,&X, y.id,&Y)) { return this->splat(X^Y); }
if (this->isImm(y.id, 0)) { return x; } // (x ^ false) == x
if (this->isImm(x.id, 0)) { return y; } // (false ^ y) == y
return {this, push(Op::bit_xor, x.id, y.id)};
}
I32 Builder::bit_clear(I32 x, I32 y) {
int X,Y;
if (this->allImm(id(x),&X, id(y),&Y)) { return this->splat(X&~Y); }
if (this->isImm(id(y), 0)) { return x; } // (x & ~false) == x
if (this->isImm(id(y),~0)) { return this->splat(0); } // (x & ~true) == false
if (this->isImm(id(x), 0)) { return this->splat(0); } // (false & ~y) == false
return {this, push(Op::bit_clear, id(x), id(y))};
if (this->allImm(x.id,&X, y.id,&Y)) { return this->splat(X&~Y); }
if (this->isImm(y.id, 0)) { return x; } // (x & ~false) == x
if (this->isImm(y.id,~0)) { return this->splat(0); } // (x & ~true) == false
if (this->isImm(x.id, 0)) { return this->splat(0); } // (false & ~y) == false
return {this, push(Op::bit_clear, x.id, y.id)};
}
I32 Builder::select(I32 x, I32 y, I32 z) {
int X,Y,Z;
if (this->allImm(id(x),&X, id(y),&Y, id(z),&Z)) { return this->splat(X?Y:Z); }
if (this->allImm(x.id,&X, y.id,&Y, z.id,&Z)) { return this->splat(X?Y:Z); }
// TODO: some cases to reduce to bit_and when y == 0 or z == 0?
return {this, push(Op::select, id(x), id(y), id(z))};
return {this, push(Op::select, x.id, y.id, z.id)};
}
I32 Builder::extract(I32 x, int bits, I32 z) {
int Z;
if (this->allImm(id(z),&Z) && (~0u>>bits) == (unsigned)Z) { return this->shr(x, bits); }
if (this->allImm(z.id,&Z) && (~0u>>bits) == (unsigned)Z) { return this->shr(x, bits); }
return this->bit_and(z, this->shr(x, bits));
}
I32 Builder::pack(I32 x, I32 y, int bits) {
int X,Y;
if (this->allImm(id(x),&X, id(y),&Y)) { return this->splat(X|(Y<<bits)); }
return {this, push(Op::pack, id(x),id(y),NA, 0,bits)};
if (this->allImm(x.id,&X, y.id,&Y)) { return this->splat(X|(Y<<bits)); }
return {this, push(Op::pack, x.id,y.id,NA, 0,bits)};
}
I32 Builder::bytes(I32 x, int control) {
return {this, push(Op::bytes, id(x),NA,NA, control)};
return {this, push(Op::bytes, x.id,NA,NA, control)};
}
F32 Builder::floor(F32 x) {
float X;
if (this->allImm(id(x),&X)) { return this->splat(floorf(X)); }
return {this, push(Op::floor, id(x))};
if (this->allImm(x.id,&X)) { return this->splat(floorf(X)); }
return {this, push(Op::floor, x.id)};
}
F32 Builder::to_f32(I32 x) {
int X;
if (this->allImm(id(x),&X)) { return this->splat((float)X); }
return {this, push(Op::to_f32, id(x))};
if (this->allImm(x.id,&X)) { return this->splat((float)X); }
return {this, push(Op::to_f32, x.id)};
}
I32 Builder::trunc(F32 x) {
float X;
if (this->allImm(id(x),&X)) { return this->splat((int)X); }
return {this, push(Op::trunc, id(x))};
if (this->allImm(x.id,&X)) { return this->splat((int)X); }
return {this, push(Op::trunc, x.id)};
}
I32 Builder::round(F32 x) {
float X;
if (this->allImm(id(x),&X)) { return this->splat((int)lrintf(X)); }
return {this, push(Op::round, id(x))};
if (this->allImm(x.id,&X)) { return this->splat((int)lrintf(X)); }
return {this, push(Op::round, x.id)};
}
F32 Builder::from_unorm(int bits, I32 x) {
@ -1107,7 +1091,7 @@ namespace skvm {
from_unorm(5, extract(bgr, 11, 0b011'111)),
from_unorm(6, extract(bgr, 5, 0b111'111)),
from_unorm(5, extract(bgr, 0, 0b011'111)),
1.0f,
splat(1.0f),
};
}
@ -1289,7 +1273,7 @@ namespace skvm {
switch (mode) {
default: SkASSERT(false); /*but also, for safety, fallthrough*/
case SkBlendMode::kClear: return { 0.0f, 0.0f, 0.0f, 0.0f };
case SkBlendMode::kClear: return { splat(0.0f), splat(0.0f), splat(0.0f), splat(0.0f) };
case SkBlendMode::kSrc: return src;
case SkBlendMode::kDst: return dst;

View File

@ -339,88 +339,53 @@ namespace skvm {
};
using Val = int;
// We reserve impossibe Val IDs as a sentinels:
// - NA meaning none, n/a, null, nil, etc.
// - IMM meaning an unresolved immediate.
static const Val NA = -1,
IMM = -2;
// We reserve an impossibe Val ID as a sentinel
// NA meaning none, n/a, null, nil, etc.
static const Val NA = -1;
struct Arg { int ix; };
struct I32 {
public:
I32() : fBuilder(nullptr), fID( NA), fImm(0) {}
I32(int x) : fBuilder(nullptr), fID(IMM), fImm(x) {}
I32(Builder* builder, Val id) : fBuilder(builder), fID( id), fImm(0) {}
explicit operator bool() const { return fID != NA; }
Builder* builder() const { return fBuilder; }
// Gets our ID, resolving any immediate to a splat() on b. Mostly for internal use.
Val resolve(Builder* b);
private:
Builder* fBuilder = nullptr;
Val fID = NA;
int fImm = 0;
Builder* builder = nullptr;
Val id = NA;
explicit operator bool() const { return id != NA; }
};
static inline I32& operator+=(I32&, I32);
static inline I32& operator-=(I32&, I32);
static inline I32& operator*=(I32&, I32);
static inline I32 operator+(I32, I32);
static inline I32 operator-(I32, I32);
static inline I32 operator*(I32, I32);
static inline I32 min(I32, I32);
static inline I32 max(I32, I32);
static inline I32 operator==(I32, I32);
static inline I32 operator!=(I32, I32);
static inline I32 operator< (I32, I32);
static inline I32 operator<=(I32, I32);
static inline I32 operator> (I32, I32);
static inline I32 operator>=(I32, I32);
struct F32 {
public:
F32() : fBuilder(nullptr), fID( NA), fImm(0) {}
F32(float x) : fBuilder(nullptr), fID(IMM), fImm(x) {}
F32(Builder* builder, Val id) : fBuilder(builder), fID( id), fImm(0) {}
explicit operator bool() const { return fID != NA; }
Builder* builder() const { return fBuilder; }
// Gets our ID, resolving any immediate to a splat() on b. Mostly for internal use.
Val resolve(Builder* b);
private:
Builder* fBuilder = nullptr;
Val fID = NA;
float fImm = 0;
Builder* builder = nullptr;
Val id = NA;
explicit operator bool() const { return id != NA; }
};
static inline F32& operator+=(F32&, F32);
static inline F32& operator-=(F32&, F32);
static inline F32& operator*=(F32&, F32);
static inline F32& operator/=(F32&, F32);
// Some operations make sense with immediate arguments,
// so we use I32a and F32a 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,
// and even sometimes not for binary operations, e.g.
//
// div(x,y) -- normal every day divide
// div(3.0f,y) -- yep, makes sense
// div(x,3.0f) -- omitted as a reminder you probably want mul(x, 1/3.0f).
//
// You can of course always splat() to override these opinions.
struct I32a {
I32a(I32 v) : SkDEBUGCODE(builder(v.builder),) id(v.id) {}
I32a(int v) : imm(v) {}
static inline F32 operator+(F32, F32);
static inline F32 operator-(F32, F32);
static inline F32 operator*(F32, F32);
static inline F32 operator/(F32, F32);
SkDEBUGCODE(Builder* builder = nullptr;)
Val id = NA;
int imm = 0;
};
static inline F32 min(F32, F32);
static inline F32 max(F32, F32);
static inline I32 operator==(F32, F32);
static inline I32 operator!=(F32, F32);
static inline I32 operator< (F32, F32);
static inline I32 operator<=(F32, F32);
static inline I32 operator> (F32, F32);
static inline I32 operator>=(F32, F32);
struct F32a {
F32a(F32 v) : SkDEBUGCODE(builder(v.builder),) id(v.id) {}
F32a(float v) : imm(v) {}
SkDEBUGCODE(Builder* builder = nullptr;)
Val id = NA;
float imm = 0;
};
struct Color {
skvm::F32 r,g,b,a;
@ -473,8 +438,8 @@ namespace skvm {
// Assert cond is true, printing debug when not.
void assert_true(I32 cond, I32 debug);
void assert_true(I32 cond, F32 debug) { this->assert_true(cond, this->bit_cast(debug)); }
void assert_true(I32 cond) { this->assert_true(cond, cond); }
void assert_true(I32 cond, F32 debug) { assert_true(cond, bit_cast(debug)); }
void assert_true(I32 cond) { assert_true(cond, cond); }
// Store {8,16,32}-bit varying.
void store8 (Arg ptr, I32 val);
@ -529,103 +494,102 @@ namespace skvm {
F32 splat(float f);
// float math, comparisons, etc.
F32 add(F32 x, F32 y);
F32 sub(F32 x, F32 y);
F32 mul(F32 x, F32 y);
F32 div(F32 x, F32 y);
F32 min(F32 x, F32 y);
F32 max(F32 x, F32 y);
F32 mad(F32 x, F32 y, F32 z) { return add(mul(x,y), z); }
F32 sqrt(F32 x);
F32 add(F32, F32); F32 add(F32a x, F32a y) { return add(_(x), _(y)); }
F32 sub(F32, F32); F32 sub(F32a x, F32a y) { return sub(_(x), _(y)); }
F32 mul(F32, F32); F32 mul(F32a x, F32a y) { return mul(_(x), _(y)); }
F32 div(F32, F32); F32 div(F32a x, F32 y) { return div(_(x), y ); }
F32 min(F32, F32); F32 min(F32a x, F32a y) { return min(_(x), _(y)); }
F32 max(F32, F32); F32 max(F32a x, F32a y) { return max(_(x), _(y)); }
F32 mad(F32 x, F32 y, F32 z) { return add(mul(x,y), z); }
F32 mad(F32a x, F32a y, F32a z) { return mad(_(x), _(y), _(z)); }
F32 inv(F32 x) { return sub(1.0f, x); }
F32 negate(F32 x) { return sub(0.0f, x); }
F32 sqrt(F32);
F32 approx_log2(F32);
F32 approx_pow2(F32);
F32 approx_powf(F32 base, F32 exp);
F32 approx_log (F32 x) { return mul(0.69314718f, approx_log2(x)); }
F32 approx_exp (F32 x) { return approx_pow2(mul(x, 1.4426950408889634074f)); }
F32 approx_log(F32 x) { // return ln(2) * log2(x)
return 0.69314718f * approx_log2(x);
}
F32 approx_exp(F32 x) { // 2^(x * log2(e))
return approx_pow2(x * 1.4426950408889634074f);
}
F32 approx_powf(F32 base, F32 exp);
F32 approx_powf(F32a base, F32a exp) { return approx_powf(_(base), _(exp)); }
F32 inv(F32 x) { return 1-x; }
F32 negate(F32 x) { return 0-x; }
F32 lerp(F32 lo, F32 hi, F32 t) { return mad(sub(hi, lo), t, lo); }
F32 lerp(F32a lo, F32a hi, F32a t) { return lerp(_(lo), _(hi), _(t)); }
F32 lerp(F32 lo, F32 hi, F32 t) {
return mad(hi-lo, t, lo);
}
F32 clamp(F32 x, F32 lo, F32 hi) {
return max(lo, min(x, hi));
}
F32 clamp01(F32 x) {
return clamp(x, 0.0f, 1.0f);
}
F32 abs(F32 x) {
return bit_cast(bit_and(bit_cast(x), 0x7fff'ffff));
}
F32 fract(F32 x) {
return x - floor(x);
}
F32 norm(F32 x, F32 y) {
return sqrt(x*x + y*y);
}
I32 eq (F32 x, F32 y);
I32 neq(F32 x, F32 y);
I32 lt (F32 x, F32 y);
I32 lte(F32 x, F32 y);
I32 gt (F32 x, F32 y);
I32 gte(F32 x, F32 y);
F32 clamp(F32 x, F32 lo, F32 hi) { return max(lo, min(x, hi)); }
F32 clamp(F32a x, F32a lo, F32a hi) { return clamp(_(x), _(lo), _(hi)); }
F32 clamp01(F32 x) { return clamp(x, 0.0f, 1.0f); }
F32 abs(F32 x) { return bit_cast(bit_and(bit_cast(x), 0x7fff'ffff)); }
F32 fract(F32 x) { return sub(x, floor(x)); }
F32 floor(F32);
I32 trunc(F32 x);
I32 round(F32 x); // Round to int using current rounding mode (as if lrintf()).
I32 bit_cast(F32 x) { return {this, id(x)}; }
I32 bit_cast(F32 x) { return {x.builder, x.id}; }
F32 norm(F32 x, F32 y) {
return sqrt(add(mul(x,x),
mul(y,y)));
}
F32 norm(F32a x, F32a y) { return norm(_(x), _(y)); }
I32 eq(F32, F32); I32 eq(F32a x, F32a y) { return eq(_(x), _(y)); }
I32 neq(F32, F32); I32 neq(F32a x, F32a y) { return neq(_(x), _(y)); }
I32 lt (F32, F32); I32 lt (F32a x, F32a y) { return lt (_(x), _(y)); }
I32 lte(F32, F32); I32 lte(F32a x, F32a y) { return lte(_(x), _(y)); }
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)); }
// int math, comparisons, etc.
I32 add(I32 x, I32 y);
I32 sub(I32 x, I32 y);
I32 mul(I32 x, I32 y);
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)); }
I32 mul(I32, I32); I32 mul(I32a x, I32a y) { return mul(_(x), _(y)); }
I32 shl(I32 x, int bits);
I32 shr(I32 x, int bits);
I32 sra(I32 x, int bits);
I32 eq (I32 x, I32 y);
I32 neq(I32 x, I32 y);
I32 lt (I32 x, I32 y);
I32 lte(I32 x, I32 y);
I32 gt (I32 x, I32 y);
I32 gte(I32 x, I32 y);
I32 eq (I32 x, I32 y); I32 eq(I32a x, I32a y) { return eq(_(x), _(y)); }
I32 neq(I32 x, I32 y); I32 neq(I32a x, I32a y) { return neq(_(x), _(y)); }
I32 lt (I32 x, I32 y); I32 lt (I32a x, I32a y) { return lt (_(x), _(y)); }
I32 lte(I32 x, I32 y); I32 lte(I32a x, I32a y) { return lte(_(x), _(y)); }
I32 gt (I32 x, I32 y); I32 gt (I32a x, I32a y) { return gt (_(x), _(y)); }
I32 gte(I32 x, I32 y); I32 gte(I32a x, I32a y) { return gte(_(x), _(y)); }
F32 to_f32(I32 x);
F32 bit_cast(I32 x) { return {this, id(x)}; }
F32 bit_cast(I32 x) { return {x.builder, x.id}; }
// Treat each 32-bit lane as a pair of 16-bit ints.
I32 add_16x2(I32 x, I32 y);
I32 sub_16x2(I32 x, I32 y);
I32 mul_16x2(I32 x, I32 y);
I32 add_16x2(I32, I32); I32 add_16x2(I32a x, I32a y) { return add_16x2(_(x), _(y)); }
I32 sub_16x2(I32, I32); I32 sub_16x2(I32a x, I32a y) { return sub_16x2(_(x), _(y)); }
I32 mul_16x2(I32, I32); I32 mul_16x2(I32a x, I32a y) { return mul_16x2(_(x), _(y)); }
I32 shl_16x2(I32 x, int bits);
I32 shr_16x2(I32 x, int bits);
I32 sra_16x2(I32 x, int bits);
I32 eq_16x2(I32 x, I32 y);
I32 neq_16x2(I32 x, I32 y);
I32 lt_16x2(I32 x, I32 y);
I32 lte_16x2(I32 x, I32 y);
I32 gt_16x2(I32 x, I32 y);
I32 gte_16x2(I32 x, I32 y);
I32 eq_16x2(I32, I32); I32 eq_16x2(I32a x, I32a y) { return eq_16x2(_(x), _(y)); }
I32 neq_16x2(I32, I32); I32 neq_16x2(I32a x, I32a y) { return neq_16x2(_(x), _(y)); }
I32 lt_16x2(I32, I32); I32 lt_16x2(I32a x, I32a y) { return lt_16x2(_(x), _(y)); }
I32 lte_16x2(I32, I32); I32 lte_16x2(I32a x, I32a y) { return lte_16x2(_(x), _(y)); }
I32 gt_16x2(I32, I32); I32 gt_16x2(I32a x, I32a y) { return gt_16x2(_(x), _(y)); }
I32 gte_16x2(I32, I32); I32 gte_16x2(I32a x, I32a y) { return gte_16x2(_(x), _(y)); }
// Bitwise operations.
I32 bit_and (I32 x, I32 y);
I32 bit_or (I32 x, I32 y);
I32 bit_xor (I32 x, I32 y);
I32 bit_clear(I32 x, I32 y); // x & ~y
I32 bit_and (I32, I32); I32 bit_and (I32a x, I32a y) { return bit_and (_(x), _(y)); }
I32 bit_or (I32, I32); I32 bit_or (I32a x, I32a y) { return bit_or (_(x), _(y)); }
I32 bit_xor (I32, I32); I32 bit_xor (I32a x, I32a y) { return bit_xor (_(x), _(y)); }
I32 bit_clear(I32, I32); I32 bit_clear(I32a x, I32a y) { return bit_clear(_(x), _(y)); }
I32 min(I32 x, I32 y) { return select(x<y, x, y); }
I32 max(I32 x, I32 y) { return select(x>y, x, y); }
I32 min(I32 x, I32 y) { return select(lt(x,y), x, y); }
I32 max(I32 x, I32 y) { return select(gt(x,y), x, y); }
I32 min(I32a x, I32a y) { return min(_(x), _(y)); }
I32 max(I32a x, I32a y) { return max(_(x), _(y)); }
I32 select(I32 cond, I32 t, I32 f); // cond ? t : f
F32 select(I32 cond, F32 t, F32 f) {
@ -633,6 +597,9 @@ namespace skvm {
, this->bit_cast(f)));
}
I32 select(I32a cond, I32a t, I32a f) { return select(_(cond), _(t), _(f)); }
F32 select(I32a cond, F32a t, F32a f) { return select(_(cond), _(t), _(f)); }
// More complex operations...
// Shuffle the bytes in x according to each nibble of control, as if
@ -659,6 +626,10 @@ namespace skvm {
I32 extract(I32 x, int bits, I32 z); // (x>>bits) & z
I32 pack (I32 x, I32 y, int bits); // x | (y << bits), assuming (x & (y << bits)) == 0
I32 extract(I32a x, int bits, I32a z) { return extract(_(x), bits, _(z)); }
I32 pack (I32a x, I32a y, int bits) { return pack (_(x), _(y), bits); }
// Common idioms used in several places, worth centralizing for consistency.
F32 from_unorm(int bits, I32); // E.g. from_unorm(8, x) -> x * (1/255.0f)
I32 to_unorm(int bits, F32); // E.g. to_unorm(8, x) -> round(x * 255)
@ -690,8 +661,21 @@ namespace skvm {
Val push(Op, Val x, Val y=NA, Val z=NA, int immy=0, int immz=0);
Val id(I32 x) { return x.resolve(this); }
Val id(F32 x) { return x.resolve(this); }
I32 _(I32a x) {
if (x.id != NA) {
SkASSERT(x.builder == this);
return {this, x.id};
}
return this->splat(x.imm);
}
F32 _(F32a x) {
if (x.id != NA) {
SkASSERT(x.builder == this);
return {this, x.id};
}
return this->splat(x.imm);
}
bool allImm() const;
@ -806,63 +790,84 @@ namespace skvm {
// TODO: control flow
// TODO: 64-bit values?
static Builder* common_builder(I32 x, I32 y) {
Builder *X = x.builder(),
*Y = y.builder();
SkASSERT(X || Y);
if (X && Y) { SkASSERT(X==Y); return X; }
if (X ) { return X; }
if (Y ) { return Y; }
return nullptr;
}
static inline I32 operator+(I32 x, I32a y) { return x.builder->add(x,y); }
static inline I32 operator+(int x, I32 y) { return y.builder->add(x,y); }
static inline I32& operator+=(I32& x, I32 y) { return (x = common_builder(x,y)->add(x,y)); }
static inline I32& operator-=(I32& x, I32 y) { return (x = common_builder(x,y)->sub(x,y)); }
static inline I32& operator*=(I32& x, I32 y) { return (x = common_builder(x,y)->mul(x,y)); }
static inline I32 operator-(I32 x, I32a y) { return x.builder->sub(x,y); }
static inline I32 operator-(int x, I32 y) { return y.builder->sub(x,y); }
static inline I32 operator+(I32 x, I32 y) { return x += y; }
static inline I32 operator-(I32 x, I32 y) { return x -= y; }
static inline I32 operator*(I32 x, I32 y) { return x *= y; }
static inline I32 operator*(I32 x, I32a y) { return x.builder->mul(x,y); }
static inline I32 operator*(int x, I32 y) { return y.builder->mul(x,y); }
static inline I32 min(I32 x, I32 y) { return common_builder(x,y)->min(x,y); }
static inline I32 max(I32 x, I32 y) { return common_builder(x,y)->max(x,y); }
static inline I32 min(I32 x, I32a y) { return x.builder->min(x,y); }
static inline I32 min(int x, I32 y) { return y.builder->min(x,y); }
static inline I32 operator==(I32 x, I32 y) { return common_builder(x,y)-> eq(x,y); }
static inline I32 operator!=(I32 x, I32 y) { return common_builder(x,y)->neq(x,y); }
static inline I32 operator< (I32 x, I32 y) { return common_builder(x,y)->lt (x,y); }
static inline I32 operator<=(I32 x, I32 y) { return common_builder(x,y)->lte(x,y); }
static inline I32 operator> (I32 x, I32 y) { return common_builder(x,y)->gt (x,y); }
static inline I32 operator>=(I32 x, I32 y) { return common_builder(x,y)->gte(x,y); }
static inline I32 max(I32 x, I32a y) { return x.builder->max(x,y); }
static inline I32 max(int x, I32 y) { return y.builder->max(x,y); }
static Builder* common_builder(F32 x, F32 y) {
Builder *X = x.builder(),
*Y = y.builder();
SkASSERT(X || Y);
if (X && Y) { SkASSERT(X==Y); return X; }
if (X ) { return X; }
if (Y ) { return Y; }
return nullptr;
}
static inline I32 operator==(I32 x, I32a y) { return x.builder->eq(x,y); }
static inline I32 operator==(int x, I32 y) { return y.builder->eq(x,y); }
static inline F32& operator+=(F32& x, F32 y) { return (x = common_builder(x,y)->add(x,y)); }
static inline F32& operator-=(F32& x, F32 y) { return (x = common_builder(x,y)->sub(x,y)); }
static inline F32& operator*=(F32& x, F32 y) { return (x = common_builder(x,y)->mul(x,y)); }
static inline F32& operator/=(F32& x, F32 y) { return (x = common_builder(x,y)->div(x,y)); }
static inline I32 operator!=(I32 x, I32a y) { return x.builder->neq(x,y); }
static inline I32 operator!=(int x, I32 y) { return y.builder->neq(x,y); }
static inline F32 operator+(F32 x, F32 y) { return x += y; }
static inline F32 operator-(F32 x, F32 y) { return x -= y; }
static inline F32 operator*(F32 x, F32 y) { return x *= y; }
static inline F32 operator/(F32 x, F32 y) { return x /= y; }
static inline I32 operator< (I32 x, I32a y) { return x.builder->lt(x,y); }
static inline I32 operator< (int x, I32 y) { return y.builder->lt(x,y); }
static inline F32 min(F32 x, F32 y) { return common_builder(x,y)->min(x,y); }
static inline F32 max(F32 x, F32 y) { return common_builder(x,y)->max(x,y); }
static inline I32 operator<=(I32 x, I32a y) { return x.builder->lte(x,y); }
static inline I32 operator<=(int x, I32 y) { return y.builder->lte(x,y); }
static inline I32 operator==(F32 x, F32 y) { return common_builder(x,y)-> eq(x,y); }
static inline I32 operator!=(F32 x, F32 y) { return common_builder(x,y)->neq(x,y); }
static inline I32 operator< (F32 x, F32 y) { return common_builder(x,y)->lt (x,y); }
static inline I32 operator<=(F32 x, F32 y) { return common_builder(x,y)->lte(x,y); }
static inline I32 operator> (F32 x, F32 y) { return common_builder(x,y)->gt (x,y); }
static inline I32 operator>=(F32 x, F32 y) { return common_builder(x,y)->gte(x,y); }
static inline I32 operator> (I32 x, I32a y) { return x.builder->gt(x,y); }
static inline I32 operator> (int x, I32 y) { return y.builder->gt(x,y); }
static inline I32 operator>=(I32 x, I32a y) { return x.builder->gte(x,y); }
static inline I32 operator>=(int x, I32 y) { return y.builder->gte(x,y); }
static inline F32 operator+(F32 x, F32a y) { return x.builder->add(x,y); }
static inline F32 operator+(float x, F32 y) { return y.builder->add(x,y); }
static inline F32 operator-(F32 x, F32a y) { return x.builder->sub(x,y); }
static inline F32 operator-(float x, F32 y) { return y.builder->sub(x,y); }
static inline F32 operator*(F32 x, F32a y) { return x.builder->mul(x,y); }
static inline F32 operator*(float x, F32 y) { return y.builder->mul(x,y); }
static inline F32 operator/(F32 x, F32 y) { return x.builder->div(x,y); }
static inline F32 operator/(float x, F32 y) { return y.builder->div(x,y); }
static inline F32 min(F32 x, F32a y) { return x.builder->min(x,y); }
static inline F32 min(float x, F32 y) { return y.builder->min(x,y); }
static inline F32 max(F32 x, F32a y) { return x.builder->max(x,y); }
static inline F32 max(float x, F32 y) { return y.builder->max(x,y); }
static inline I32 operator==(F32 x, F32a y) { return x.builder->eq(x,y); }
static inline I32 operator==(float x, F32 y) { return y.builder->eq(x,y); }
static inline I32 operator!=(F32 x, F32a y) { return x.builder->neq(x,y); }
static inline I32 operator!=(float x, F32 y) { return y.builder->neq(x,y); }
static inline I32 operator< (F32 x, F32a y) { return x.builder->lt(x,y); }
static inline I32 operator< (float x, F32 y) { return y.builder->lt(x,y); }
static inline I32 operator<=(F32 x, F32a y) { return x.builder->lte(x,y); }
static inline I32 operator<=(float x, F32 y) { return y.builder->lte(x,y); }
static inline I32 operator> (F32 x, F32a y) { return x.builder->gt(x,y); }
static inline I32 operator> (float x, F32 y) { return y.builder->gt(x,y); }
static inline I32 operator>=(F32 x, F32a y) { return x.builder->gte(x,y); }
static inline I32 operator>=(float x, F32 y) { return y.builder->gte(x,y); }
static inline I32& operator+=(I32& x, I32a y) { return (x = x + y); }
static inline I32& operator-=(I32& x, I32a y) { return (x = x - y); }
static inline I32& operator*=(I32& x, I32a y) { return (x = x * y); }
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 F32& operator*=(F32& x, F32a y) { return (x = x * y); }
}

View File

@ -141,12 +141,7 @@ namespace {
// precisely which value we'll treat as which channel. Imagine the shader
// called std::swap(*r,*b)... it draws differently, but p.hash() is unchanged.
// We'll fold the hash of their IDs in order to disambiguate.
const int outputs[] = {
c.r.resolve(&p),
c.g.resolve(&p),
c.b.resolve(&p),
c.a.resolve(&p),
};
const int outputs[] = { c.r.id, c.g.id, c.b.id, c.a.id };
hash ^= SkOpts::hash(outputs, sizeof(outputs));
} else {
*ok = false;
@ -259,7 +254,7 @@ namespace {
auto load_coverage = [&](skvm::Color* cov) {
bool partial_coverage = true;
switch (params.coverage) {
case Coverage::Full: cov->r = cov->g = cov->b = cov->a = 1.0f;
case Coverage::Full: cov->r = cov->g = cov->b = cov->a = splat(1.0f);
partial_coverage = false;
break;
@ -340,7 +335,7 @@ namespace {
// When a destination is known opaque, we may assume it both starts and stays fully
// opaque, ignoring any math that disagrees. This sometimes trims a little work.
if (params.dst.isOpaque()) {
dst.a = 1.0f;
dst.a = splat(1.0f);
} else if (params.dst.alphaType() == kUnpremul_SkAlphaType) {
premul(&dst.r, &dst.g, &dst.b, dst.a);
}
@ -356,7 +351,7 @@ namespace {
}
if (params.dst.isOpaque()) {
src.a = 1.0f;
src.a = splat(1.0f);
} else if (params.dst.alphaType() == kUnpremul_SkAlphaType) {
unpremul(&src.r, &src.g, &src.b, src.a);
}