in src/sksl/codegen/SkSLWGSLCodeGenerator.cpp [3069:3347]
std::string WGSLCodeGenerator::assembleIntrinsicCall(const FunctionCall& call,
IntrinsicKind kind,
Precedence parentPrecedence) {
// Be careful: WGSL 1.0 will reject any intrinsic calls which can be constant-evaluated to
// infinity or nan with a compile error. If all arguments to an intrinsic are compile-time
// constants (`all_arguments_constant`), it is safest to copy one argument into a scratch-let so
// that the call will be seen as runtime-evaluated, which defuses the overflow checks.
// Don't worry; a competent driver should still optimize it away.
const ExpressionArray& arguments = call.arguments();
switch (kind) {
case k_atan_IntrinsicKind: {
const char* name = (arguments.size() == 1) ? "atan" : "atan2";
return this->assembleSimpleIntrinsic(name, call);
}
case k_dFdx_IntrinsicKind:
return this->assembleSimpleIntrinsic("dpdx", call);
case k_dFdy_IntrinsicKind:
// TODO(b/294274678): apply RTFlip here
return this->assembleSimpleIntrinsic("dpdy", call);
case k_dot_IntrinsicKind: {
if (arguments[0]->type().isScalar()) {
return this->assembleBinaryOpIntrinsic(OperatorKind::STAR, call, parentPrecedence);
}
return this->assembleSimpleIntrinsic("dot", call);
}
case k_equal_IntrinsicKind:
return this->assembleBinaryOpIntrinsic(OperatorKind::EQEQ, call, parentPrecedence);
case k_faceforward_IntrinsicKind: {
if (arguments[0]->type().isScalar()) {
// select(-N, N, (I * Nref) < 0)
std::string N = this->writeNontrivialScratchLet(*arguments[0],
Precedence::kAssignment);
return this->writeScratchLet(
"select(-" + N + ", " + N + ", " +
this->assembleBinaryExpression(*arguments[1],
OperatorKind::STAR,
*arguments[2],
arguments[1]->type(),
Precedence::kRelational) +
" < 0)");
}
return this->assembleSimpleIntrinsic("faceForward", call);
}
case k_frexp_IntrinsicKind:
// SkSL frexp is "$genType fract = frexp($genType, out $genIType exp)" whereas WGSL
// returns a struct with no out param: "let [fract, exp] = frexp($genType)".
return this->assembleOutAssignedIntrinsic("frexp", "fract", "exp", call);
case k_greaterThan_IntrinsicKind:
return this->assembleBinaryOpIntrinsic(OperatorKind::GT, call, parentPrecedence);
case k_greaterThanEqual_IntrinsicKind:
return this->assembleBinaryOpIntrinsic(OperatorKind::GTEQ, call, parentPrecedence);
case k_inverse_IntrinsicKind:
return this->assembleInversePolyfill(call);
case k_inversesqrt_IntrinsicKind:
return this->assembleSimpleIntrinsic("inverseSqrt", call);
case k_lessThan_IntrinsicKind:
return this->assembleBinaryOpIntrinsic(OperatorKind::LT, call, parentPrecedence);
case k_lessThanEqual_IntrinsicKind:
return this->assembleBinaryOpIntrinsic(OperatorKind::LTEQ, call, parentPrecedence);
case k_matrixCompMult_IntrinsicKind: {
// We use a scratch-let for arg0 to avoid the potential for WGSL overflow. (skia:14385)
std::string arg0 = all_arguments_constant(arguments)
? this->writeScratchLet(*arguments[0], Precedence::kPostfix)
: this->writeNontrivialScratchLet(*arguments[0], Precedence::kPostfix);
std::string arg1 = this->writeNontrivialScratchLet(*arguments[1], Precedence::kPostfix);
return this->writeScratchLet(
this->assembleComponentwiseMatrixBinary(arguments[0]->type(),
arguments[1]->type(),
arg0,
arg1,
OperatorKind::STAR));
}
case k_mix_IntrinsicKind: {
const char* name = arguments[2]->type().componentType().isBoolean() ? "select" : "mix";
return this->assembleVectorizedIntrinsic(name, call);
}
case k_mod_IntrinsicKind: {
// WGSL has no intrinsic equivalent to `mod`. Synthesize `x - y * floor(x / y)`.
// We can use a scratch-let on one side to dodge WGSL overflow errors. In practice, I
// can't find any values of x or y which would overflow, but it can't hurt. (skia:14385)
std::string arg0 = all_arguments_constant(arguments)
? this->writeScratchLet(*arguments[0], Precedence::kAdditive)
: this->writeNontrivialScratchLet(*arguments[0], Precedence::kAdditive);
std::string arg1 = this->writeNontrivialScratchLet(*arguments[1],
Precedence::kAdditive);
return this->writeScratchLet(arg0 + " - " + arg1 + " * floor(" +
arg0 + " / " + arg1 + ")");
}
case k_modf_IntrinsicKind:
// SkSL modf is "$genType fract = modf($genType, out $genType whole)" whereas WGSL
// returns a struct with no out param: "let [fract, whole] = modf($genType)".
return this->assembleOutAssignedIntrinsic("modf", "fract", "whole", call);
case k_normalize_IntrinsicKind: {
const char* name = arguments[0]->type().isScalar() ? "sign" : "normalize";
return this->assembleSimpleIntrinsic(name, call);
}
case k_not_IntrinsicKind:
return this->assembleUnaryOpIntrinsic(OperatorKind::LOGICALNOT, call, parentPrecedence);
case k_notEqual_IntrinsicKind:
return this->assembleBinaryOpIntrinsic(OperatorKind::NEQ, call, parentPrecedence);
case k_packHalf2x16_IntrinsicKind:
return this->assembleSimpleIntrinsic("pack2x16float", call);
case k_packSnorm2x16_IntrinsicKind:
return this->assembleSimpleIntrinsic("pack2x16snorm", call);
case k_packSnorm4x8_IntrinsicKind:
return this->assembleSimpleIntrinsic("pack4x8snorm", call);
case k_packUnorm2x16_IntrinsicKind:
return this->assembleSimpleIntrinsic("pack2x16unorm", call);
case k_packUnorm4x8_IntrinsicKind:
return this->assembleSimpleIntrinsic("pack4x8unorm", call);
case k_reflect_IntrinsicKind:
if (arguments[0]->type().isScalar()) {
// I - 2 * N * I * N
// We can use a scratch-let for N to dodge WGSL overflow errors. (skia:14385)
std::string I = this->writeNontrivialScratchLet(*arguments[0],
Precedence::kAdditive);
std::string N = all_arguments_constant(arguments)
? this->writeScratchLet(*arguments[1], Precedence::kMultiplicative)
: this->writeNontrivialScratchLet(*arguments[1], Precedence::kMultiplicative);
return this->writeScratchLet(String::printf("%s - 2 * %s * %s * %s",
I.c_str(), N.c_str(),
I.c_str(), N.c_str()));
}
return this->assembleSimpleIntrinsic("reflect", call);
case k_refract_IntrinsicKind:
if (arguments[0]->type().isScalar()) {
// WGSL only implements refract for vectors; rather than reimplementing refract from
// scratch, we can replace the call with `refract(float2(I,0), float2(N,0), eta).x`.
std::string I = this->writeNontrivialScratchLet(*arguments[0],
Precedence::kSequence);
std::string N = this->writeNontrivialScratchLet(*arguments[1],
Precedence::kSequence);
// We can use a scratch-let for Eta to avoid WGSL overflow errors. (skia:14385)
std::string Eta = all_arguments_constant(arguments)
? this->writeScratchLet(*arguments[2], Precedence::kSequence)
: this->writeNontrivialScratchLet(*arguments[2], Precedence::kSequence);
return this->writeScratchLet(
String::printf("refract(vec2<%s>(%s, 0), vec2<%s>(%s, 0), %s).x",
to_wgsl_type(fContext, arguments[0]->type()).c_str(),
I.c_str(),
to_wgsl_type(fContext, arguments[1]->type()).c_str(),
N.c_str(),
Eta.c_str()));
}
return this->assembleSimpleIntrinsic("refract", call);
case k_sample_IntrinsicKind: {
// Determine if a bias argument was passed in.
SkASSERT(arguments.size() == 2 || arguments.size() == 3);
bool callIncludesBias = (arguments.size() == 3);
if (fProgram.fConfig->fSettings.fSharpenTextures || callIncludesBias) {
// We need to supply a bias argument; this is a separate intrinsic in WGSL.
std::string expr = this->assemblePartialSampleCall("textureSampleBias",
*arguments[0],
*arguments[1]);
expr += ", ";
if (callIncludesBias) {
expr += this->assembleExpression(*arguments[2], Precedence::kAdditive) +
" + ";
}
expr += skstd::to_string(fProgram.fConfig->fSettings.fSharpenTextures
? kSharpenTexturesBias
: 0.0f);
return expr + ')';
}
// No bias is necessary, so we can call `textureSample` directly.
return this->assemblePartialSampleCall("textureSample",
*arguments[0],
*arguments[1]) + ')';
}
case k_sampleLod_IntrinsicKind: {
std::string expr = this->assemblePartialSampleCall("textureSampleLevel",
*arguments[0],
*arguments[1]);
expr += ", " + this->assembleExpression(*arguments[2], Precedence::kSequence);
return expr + ')';
}
case k_sampleGrad_IntrinsicKind: {
std::string expr = this->assemblePartialSampleCall("textureSampleGrad",
*arguments[0],
*arguments[1]);
expr += ", " + this->assembleExpression(*arguments[2], Precedence::kSequence);
expr += ", " + this->assembleExpression(*arguments[3], Precedence::kSequence);
return expr + ')';
}
case k_textureHeight_IntrinsicKind:
return this->assembleSimpleIntrinsic("textureDimensions", call) + ".y";
case k_textureRead_IntrinsicKind: {
// We need to inject an extra argument for the mip-level. We don't plan on using mipmaps
// in our storage textures, so we can just pass zero.
std::string tex = this->assembleExpression(*arguments[0], Precedence::kSequence);
std::string pos = this->writeScratchLet(*arguments[1], Precedence::kSequence);
return std::string("textureLoad(") + tex + ", " + pos + ", 0)";
}
case k_textureWidth_IntrinsicKind:
return this->assembleSimpleIntrinsic("textureDimensions", call) + ".x";
case k_textureWrite_IntrinsicKind:
return this->assembleSimpleIntrinsic("textureStore", call);
case k_unpackHalf2x16_IntrinsicKind:
return this->assembleSimpleIntrinsic("unpack2x16float", call);
case k_unpackSnorm2x16_IntrinsicKind:
return this->assembleSimpleIntrinsic("unpack2x16snorm", call);
case k_unpackSnorm4x8_IntrinsicKind:
return this->assembleSimpleIntrinsic("unpack4x8snorm", call);
case k_unpackUnorm2x16_IntrinsicKind:
return this->assembleSimpleIntrinsic("unpack2x16unorm", call);
case k_unpackUnorm4x8_IntrinsicKind:
return this->assembleSimpleIntrinsic("unpack4x8unorm", call);
case k_clamp_IntrinsicKind:
case k_max_IntrinsicKind:
case k_min_IntrinsicKind:
case k_smoothstep_IntrinsicKind:
case k_step_IntrinsicKind:
return this->assembleVectorizedIntrinsic(call.function().name(), call);
case k_abs_IntrinsicKind:
case k_acos_IntrinsicKind:
case k_all_IntrinsicKind:
case k_any_IntrinsicKind:
case k_asin_IntrinsicKind:
case k_atomicAdd_IntrinsicKind:
case k_atomicLoad_IntrinsicKind:
case k_atomicStore_IntrinsicKind:
case k_ceil_IntrinsicKind:
case k_cos_IntrinsicKind:
case k_cross_IntrinsicKind:
case k_degrees_IntrinsicKind:
case k_distance_IntrinsicKind:
case k_exp_IntrinsicKind:
case k_exp2_IntrinsicKind:
case k_floor_IntrinsicKind:
case k_fract_IntrinsicKind:
case k_length_IntrinsicKind:
case k_log_IntrinsicKind:
case k_log2_IntrinsicKind:
case k_radians_IntrinsicKind:
case k_pow_IntrinsicKind:
case k_saturate_IntrinsicKind:
case k_sign_IntrinsicKind:
case k_sin_IntrinsicKind:
case k_sqrt_IntrinsicKind:
case k_storageBarrier_IntrinsicKind:
case k_tan_IntrinsicKind:
case k_workgroupBarrier_IntrinsicKind:
default:
return this->assembleSimpleIntrinsic(call.function().name(), call);
}
}