Add most important intrinsics to the interpreter
Started with component-wise comparison and mix builtins.
Implemented min, max, clamp, and saturate using those.
Moved dot to SkSL as well. Because we now have builtins
implemented using other (intrinsic) builtins, I had to
split the include file in two - this lets the intrinsics
be marked so we can call them from the second phase of
builtins that are written in SkSL.
Given that the comparisons and kSelect are now used by
these, I added vector versions of those instructions.
I also switched the kSelect args to match GLSL mix(),
mostly so the logic of mapping intrinsic arguments to
instruction register args remains simple.
Inspired by the (never-landed):
https://skia-review.googlesource.com/c/skia/+/230739
Change-Id: Iecb0a7e8dc633625ff2cada7fb962bf2137fa881
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/272516
Reviewed-by: Ethan Nicholas <ethannicholas@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/src/sksl/SkSLByteCodeGenerator.cpp b/src/sksl/SkSLByteCodeGenerator.cpp
index 3a65d8c..b8cd2ac 100644
--- a/src/sksl/SkSLByteCodeGenerator.cpp
+++ b/src/sksl/SkSLByteCodeGenerator.cpp
@@ -14,14 +14,37 @@
: INHERITED(program, errors, nullptr)
, fOutput(output)
, fIntrinsics {
- // "Normal" intrinsics are all $genType f($genType), mapped to a single instruction
- { "cos", ByteCode::Instruction::kCos },
- { "sin", ByteCode::Instruction::kSin },
- { "sqrt", ByteCode::Instruction::kSqrt },
- { "tan", ByteCode::Instruction::kTan },
+ // "Normal" intrinsics are all $genType f($genType [, $genType...])
+ // and all map to a single instruction (possibly with a vector version)
+ { "cos", { ByteCode::Instruction::kCos, false } },
+ { "mix", { ByteCode::Instruction::kSelect, true } },
+ { "not", { ByteCode::Instruction::kNot, false } },
+ { "sin", { ByteCode::Instruction::kSin, false } },
+ { "sqrt", { ByteCode::Instruction::kSqrt, false } },
+ { "tan", { ByteCode::Instruction::kTan, false } },
+
+ { "lessThan", { ByteCode::Instruction::kCompareLTF,
+ ByteCode::Instruction::kCompareLTS,
+ ByteCode::Instruction::kCompareLTU, true } },
+ { "lessThanEqual", { ByteCode::Instruction::kCompareLTEQF,
+ ByteCode::Instruction::kCompareLTEQS,
+ ByteCode::Instruction::kCompareLTEQU, true } },
+ { "greaterThan", { ByteCode::Instruction::kCompareGTF,
+ ByteCode::Instruction::kCompareGTS,
+ ByteCode::Instruction::kCompareGTU, true } },
+ { "greaterThanEqual", { ByteCode::Instruction::kCompareGTEQF,
+ ByteCode::Instruction::kCompareGTEQS,
+ ByteCode::Instruction::kCompareGTEQU, true } },
+ { "equal", { ByteCode::Instruction::kCompareEQF,
+ ByteCode::Instruction::kCompareEQI,
+ ByteCode::Instruction::kCompareEQI, true } },
+ { "notEqual", { ByteCode::Instruction::kCompareNEQF,
+ ByteCode::Instruction::kCompareNEQI,
+ ByteCode::Instruction::kCompareNEQI, true } },
// Special intrinsics have other signatures, or non-standard code-gen
- { "dot", SpecialIntrinsic::kDot },
+ { "all", SpecialIntrinsic::kAll },
+ { "any", SpecialIntrinsic::kAny },
{ "inverse", SpecialIntrinsic::kInverse },
{ "print", SpecialIntrinsic::kPrint },
} {}
@@ -914,33 +937,22 @@
ByteCode::Register result) {
if (intrinsic.fIsSpecial) {
switch (intrinsic.fValue.fSpecial) {
- case SpecialIntrinsic::kDot: {
- SkASSERT(c.fArguments.size() == 2);
+ case SpecialIntrinsic::kAll:
+ case SpecialIntrinsic::kAny: {
+ SkASSERT(c.fArguments.size() == 1);
int count = SlotCount(c.fArguments[0]->fType);
- ByteCode::Register left = this->next(count);
- this->writeExpression(*c.fArguments[0], left);
- ByteCode::Register right = this->next(count);
- this->writeExpression(*c.fArguments[1], right);
- ByteCode::Register product = this->next(count);
- this->writeTypedInstruction(c.fType,
- ByteCode::Instruction::kMultiplyIN,
- ByteCode::Instruction::kMultiplyIN,
- ByteCode::Instruction::kMultiplyFN);
- this->write((uint8_t) count);
- this->write(product);
- this->write(left);
- this->write(right);
- ByteCode::Register total = product;
+ SkASSERT(count > 1);
+ // Fold a bvec down to a single bool:
+ ByteCode::Register arg = this->next(count);
+ ByteCode::Instruction inst = intrinsic.fValue.fSpecial == SpecialIntrinsic::kAll
+ ? ByteCode::Instruction::kAnd
+ : ByteCode::Instruction::kOr;
+ this->writeExpression(*c.fArguments[0], arg);
for (int i = 1; i < count; ++i) {
- this->writeTypedInstruction(c.fType,
- ByteCode::Instruction::kAddI,
- ByteCode::Instruction::kAddI,
- ByteCode::Instruction::kAddF);
- ByteCode::Register sum = i == count - 1 ? result : this->next(1);
- this->write(sum);
- this->write(total);
- this->write(product + i);
- total = sum;
+ this->write(inst);
+ this->write(result);
+ this->write(i == 1 ? arg : result);
+ this->write(arg + i);
}
break;
}
@@ -970,7 +982,7 @@
}
}
} else {
- int count = SlotCount(c.fType);
+ uint8_t count = (uint8_t) SlotCount(c.fType);
std::vector<ByteCode::Register> argRegs;
for (const auto& expr : c.fArguments) {
SkASSERT(SlotCount(expr->fType) == count);
@@ -978,21 +990,49 @@
this->writeExpression(*expr, reg);
argRegs.push_back(reg);
}
- for (int i = 0; i < count; ++i) {
- this->write(intrinsic.fValue.fInstruction);
- if (c.fType.fName != "void") {
- this->write(result + i);
+
+ const auto& instructions = intrinsic.fValue.fInstructions;
+ const Type& opType = c.fArguments[0]->fType;
+
+ if (instructions.fUseVector) {
+ if (count == 1) {
+ this->writeTypedInstruction(opType,
+ instructions.fFloat,
+ instructions.fSigned,
+ instructions.fUnsigned);
+ } else {
+ this->writeTypedInstruction(opType,
+ VEC(instructions.fFloat),
+ VEC(instructions.fSigned),
+ VEC(instructions.fUnsigned));
+ this->write(count);
}
+ this->write(result);
for (ByteCode::Register arg : argRegs) {
- this->write(arg + i);
+ this->write(arg);
+ }
+ } else {
+ // No vector version of the instruction exists. Emit the scalar instruction N times.
+ for (uint8_t i = 0; i < count; ++i) {
+ this->writeTypedInstruction(opType,
+ instructions.fFloat,
+ instructions.fSigned,
+ instructions.fUnsigned);
+ this->write(result + i);
+ for (ByteCode::Register arg : argRegs) {
+ this->write(arg + i);
+ }
}
}
}
}
void ByteCodeGenerator::writeFunctionCall(const FunctionCall& c, ByteCode::Register result) {
+ // 'mix' is present as both a "pure" intrinsic (fDefined == false), and an SkSL implementation
+ // in the pre-parsed include files (fDefined == true), depending on argument types. We only
+ // send calls to the former through the intrinsic path here.
auto found = fIntrinsics.find(c.fFunction.fName);
- if (found != fIntrinsics.end()) {
+ if (found != fIntrinsics.end() && !c.fFunction.fDefined) {
return this->writeIntrinsicCall(c, found->second, result);
}
int argCount = c.fArguments.size();
@@ -1162,9 +1202,9 @@
for (int i = 0; i < count; ++i) {
this->write(ByteCode::Instruction::kSelect);
this->write(result + i);
- this->write(test);
- this->write(ifTrue + i);
this->write(ifFalse + i);
+ this->write(ifTrue + i);
+ this->write(test);
}
}