in src/sksl/codegen/SkSLSPIRVCodeGenerator.cpp [2097:2441]
SpvId SPIRVCodeGenerator::writeSpecialIntrinsic(const FunctionCall& c, SpecialIntrinsic kind,
OutputStream& out) {
const ExpressionArray& arguments = c.arguments();
const Type& callType = c.type();
// Note: MatrixCompMult creates its own result ID, avoid calling nextId as it could generate a
// RelaxedPrecision decoration on an ID that would never be used.
SpvId result = kind == kMatrixCompMult_SpecialIntrinsic ? 0 : this->nextId(&callType);
switch (kind) {
case kAtan_SpecialIntrinsic: {
STArray<2, SpvId> argumentIds;
for (const std::unique_ptr<Expression>& arg : arguments) {
argumentIds.push_back(this->writeExpression(*arg, out));
}
this->writeOpCode(SpvOpExtInst, 5 + (int32_t) argumentIds.size(), out);
this->writeWord(this->getType(callType), out);
this->writeWord(result, out);
this->writeWord(fGLSLExtendedInstructions, out);
this->writeWord(argumentIds.size() == 2 ? GLSLstd450Atan2 : GLSLstd450Atan, out);
for (SpvId id : argumentIds) {
this->writeWord(id, out);
}
break;
}
case kSampledImage_SpecialIntrinsic: {
SkASSERT(arguments.size() == 2);
SpvId img = this->writeExpression(*arguments[0], out);
SpvId sampler = this->writeExpression(*arguments[1], out);
this->writeInstruction(SpvOpSampledImage,
this->getType(callType),
result,
img,
sampler,
out);
break;
}
case kSubpassLoad_SpecialIntrinsic: {
SpvId img = this->writeExpression(*arguments[0], out);
ExpressionArray args;
args.reserve_exact(2);
args.push_back(Literal::MakeInt(fContext, Position(), /*value=*/0));
args.push_back(Literal::MakeInt(fContext, Position(), /*value=*/0));
ConstructorCompound ctor(Position(), *fContext.fTypes.fInt2, std::move(args));
SpvId coords = this->writeExpression(ctor, out);
if (arguments.size() == 1) {
this->writeInstruction(SpvOpImageRead,
this->getType(callType),
result,
img,
coords,
out);
} else {
SkASSERT(arguments.size() == 2);
SpvId sample = this->writeExpression(*arguments[1], out);
this->writeInstruction(SpvOpImageRead,
this->getType(callType),
result,
img,
coords,
SpvImageOperandsSampleMask,
sample,
out);
}
break;
}
case kTexture_SpecialIntrinsic: {
SpvOp_ op = SpvOpImageSampleImplicitLod;
const Type& arg1Type = arguments[1]->type();
switch (arguments[0]->type().dimensions()) {
case SpvDim1D:
if (arg1Type.matches(*fContext.fTypes.fFloat2)) {
op = SpvOpImageSampleProjImplicitLod;
} else {
SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat));
}
break;
case SpvDim2D:
if (arg1Type.matches(*fContext.fTypes.fFloat3)) {
op = SpvOpImageSampleProjImplicitLod;
} else {
SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat2));
}
break;
case SpvDim3D:
if (arg1Type.matches(*fContext.fTypes.fFloat4)) {
op = SpvOpImageSampleProjImplicitLod;
} else {
SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat3));
}
break;
case SpvDimCube: // fall through
case SpvDimRect: // fall through
case SpvDimBuffer: // fall through
case SpvDimSubpassData:
break;
}
SpvId type = this->getType(callType);
SpvId sampler = this->writeExpression(*arguments[0], out);
SpvId uv = this->writeExpression(*arguments[1], out);
if (arguments.size() == 3) {
this->writeInstruction(op, type, result, sampler, uv,
SpvImageOperandsBiasMask,
this->writeExpression(*arguments[2], out),
out);
} else {
SkASSERT(arguments.size() == 2);
if (fProgram.fConfig->fSettings.fSharpenTextures) {
SpvId lodBias = this->writeLiteral(kSharpenTexturesBias,
*fContext.fTypes.fFloat);
this->writeInstruction(op, type, result, sampler, uv,
SpvImageOperandsBiasMask, lodBias, out);
} else {
this->writeInstruction(op, type, result, sampler, uv,
out);
}
}
break;
}
case kTextureGrad_SpecialIntrinsic: {
SpvOp_ op = SpvOpImageSampleExplicitLod;
SkASSERT(arguments.size() == 4);
SkASSERT(arguments[0]->type().dimensions() == SpvDim2D);
SkASSERT(arguments[1]->type().matches(*fContext.fTypes.fFloat2));
SkASSERT(arguments[2]->type().matches(*fContext.fTypes.fFloat2));
SkASSERT(arguments[3]->type().matches(*fContext.fTypes.fFloat2));
SpvId type = this->getType(callType);
SpvId sampler = this->writeExpression(*arguments[0], out);
SpvId uv = this->writeExpression(*arguments[1], out);
SpvId dPdx = this->writeExpression(*arguments[2], out);
SpvId dPdy = this->writeExpression(*arguments[3], out);
this->writeInstruction(op, type, result, sampler, uv, SpvImageOperandsGradMask,
dPdx, dPdy, out);
break;
}
case kTextureLod_SpecialIntrinsic: {
SpvOp_ op = SpvOpImageSampleExplicitLod;
SkASSERT(arguments.size() == 3);
SkASSERT(arguments[0]->type().dimensions() == SpvDim2D);
SkASSERT(arguments[2]->type().matches(*fContext.fTypes.fFloat));
const Type& arg1Type = arguments[1]->type();
if (arg1Type.matches(*fContext.fTypes.fFloat3)) {
op = SpvOpImageSampleProjExplicitLod;
} else {
SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat2));
}
SpvId type = this->getType(callType);
SpvId sampler = this->writeExpression(*arguments[0], out);
SpvId uv = this->writeExpression(*arguments[1], out);
this->writeInstruction(op, type, result, sampler, uv,
SpvImageOperandsLodMask,
this->writeExpression(*arguments[2], out),
out);
break;
}
case kTextureRead_SpecialIntrinsic: {
SkASSERT(arguments[0]->type().dimensions() == SpvDim2D);
SkASSERT(arguments[1]->type().matches(*fContext.fTypes.fUInt2));
SpvId type = this->getType(callType);
SpvId image = this->writeExpression(*arguments[0], out);
SpvId coord = this->writeExpression(*arguments[1], out);
const Type& arg0Type = arguments[0]->type();
SkASSERT(arg0Type.typeKind() == Type::TypeKind::kTexture);
switch (arg0Type.textureAccess()) {
case Type::TextureAccess::kSample:
this->writeInstruction(SpvOpImageFetch, type, result, image, coord,
SpvImageOperandsLodMask,
this->writeOpConstant(*fContext.fTypes.fInt, 0),
out);
break;
case Type::TextureAccess::kRead:
case Type::TextureAccess::kReadWrite:
this->writeInstruction(SpvOpImageRead, type, result, image, coord, out);
break;
case Type::TextureAccess::kWrite:
default:
SkDEBUGFAIL("'textureRead' called on writeonly texture type");
break;
}
break;
}
case kTextureWrite_SpecialIntrinsic: {
SkASSERT(arguments[0]->type().dimensions() == SpvDim2D);
SkASSERT(arguments[1]->type().matches(*fContext.fTypes.fUInt2));
SkASSERT(arguments[2]->type().matches(*fContext.fTypes.fHalf4));
SkASSERT(!callType.hasPrecision());
SpvId image = this->writeExpression(*arguments[0], out);
SpvId coord = this->writeExpression(*arguments[1], out);
SpvId texel = this->writeExpression(*arguments[2], out);
this->writeInstruction(SpvOpImageWrite, image, coord, texel, out);
break;
}
case kTextureWidth_SpecialIntrinsic:
case kTextureHeight_SpecialIntrinsic: {
SkASSERT(arguments[0]->type().dimensions() == SpvDim2D);
fCapabilities |= 1ULL << SpvCapabilityImageQuery;
SpvId dimsType = this->getType(*fContext.fTypes.fUInt2);
SpvId dims = this->nextId(nullptr);
SpvId image = this->writeExpression(*arguments[0], out);
this->writeInstruction(SpvOpImageQuerySize, dimsType, dims, image, out);
SpvId type = this->getType(callType);
int32_t index = (kind == kTextureWidth_SpecialIntrinsic) ? 0 : 1;
this->writeInstruction(SpvOpCompositeExtract, type, result, dims, index, out);
break;
}
case kMod_SpecialIntrinsic: {
TArray<SpvId> args = this->vectorize(arguments, out);
SkASSERT(args.size() == 2);
const Type& operandType = arguments[0]->type();
SpvOp_ op = pick_by_type(operandType, SpvOpFMod, SpvOpSMod, SpvOpUMod, SpvOpUndef);
SkASSERT(op != SpvOpUndef);
this->writeOpCode(op, 5, out);
this->writeWord(this->getType(operandType), out);
this->writeWord(result, out);
this->writeWord(args[0], out);
this->writeWord(args[1], out);
break;
}
case kDFdy_SpecialIntrinsic: {
SpvId fn = this->writeExpression(*arguments[0], out);
this->writeOpCode(SpvOpDPdy, 4, out);
this->writeWord(this->getType(callType), out);
this->writeWord(result, out);
this->writeWord(fn, out);
if (!fProgram.fConfig->fSettings.fForceNoRTFlip) {
this->addRTFlipUniform(c.fPosition);
ComponentArray componentArray;
for (int index = 0; index < callType.columns(); ++index) {
componentArray.push_back(SwizzleComponent::Y);
}
SpvId rtFlipY = this->writeSwizzle(*this->identifier(SKSL_RTFLIP_NAME),
componentArray, out);
SpvId flipped = this->nextId(&callType);
this->writeInstruction(SpvOpFMul, this->getType(callType), flipped, result,
rtFlipY, out);
result = flipped;
}
break;
}
case kClamp_SpecialIntrinsic: {
TArray<SpvId> args = this->vectorize(arguments, out);
SkASSERT(args.size() == 3);
this->writeGLSLExtendedInstruction(callType, result, GLSLstd450FClamp, GLSLstd450SClamp,
GLSLstd450UClamp, args, out);
break;
}
case kMax_SpecialIntrinsic: {
TArray<SpvId> args = this->vectorize(arguments, out);
SkASSERT(args.size() == 2);
this->writeGLSLExtendedInstruction(callType, result, GLSLstd450FMax, GLSLstd450SMax,
GLSLstd450UMax, args, out);
break;
}
case kMin_SpecialIntrinsic: {
TArray<SpvId> args = this->vectorize(arguments, out);
SkASSERT(args.size() == 2);
this->writeGLSLExtendedInstruction(callType, result, GLSLstd450FMin, GLSLstd450SMin,
GLSLstd450UMin, args, out);
break;
}
case kMix_SpecialIntrinsic: {
TArray<SpvId> args = this->vectorize(arguments, out);
SkASSERT(args.size() == 3);
if (arguments[2]->type().componentType().isBoolean()) {
// Use OpSelect to implement Boolean mix().
SpvId falseId = this->writeExpression(*arguments[0], out);
SpvId trueId = this->writeExpression(*arguments[1], out);
SpvId conditionId = this->writeExpression(*arguments[2], out);
this->writeInstruction(SpvOpSelect, this->getType(arguments[0]->type()), result,
conditionId, trueId, falseId, out);
} else {
this->writeGLSLExtendedInstruction(callType, result, GLSLstd450FMix, SpvOpUndef,
SpvOpUndef, args, out);
}
break;
}
case kSaturate_SpecialIntrinsic: {
SkASSERT(arguments.size() == 1);
int width = arguments[0]->type().columns();
STArray<3, SpvId> spvArgs{
this->vectorize(*arguments[0], width, out),
this->vectorize(*Literal::MakeFloat(fContext, Position(), /*value=*/0), width, out),
this->vectorize(*Literal::MakeFloat(fContext, Position(), /*value=*/1), width, out),
};
this->writeGLSLExtendedInstruction(callType, result, GLSLstd450FClamp, GLSLstd450SClamp,
GLSLstd450UClamp, spvArgs, out);
break;
}
case kSmoothStep_SpecialIntrinsic: {
TArray<SpvId> args = this->vectorize(arguments, out);
SkASSERT(args.size() == 3);
this->writeGLSLExtendedInstruction(callType, result, GLSLstd450SmoothStep, SpvOpUndef,
SpvOpUndef, args, out);
break;
}
case kStep_SpecialIntrinsic: {
TArray<SpvId> args = this->vectorize(arguments, out);
SkASSERT(args.size() == 2);
this->writeGLSLExtendedInstruction(callType, result, GLSLstd450Step, SpvOpUndef,
SpvOpUndef, args, out);
break;
}
case kMatrixCompMult_SpecialIntrinsic: {
SkASSERT(arguments.size() == 2);
SpvId lhs = this->writeExpression(*arguments[0], out);
SpvId rhs = this->writeExpression(*arguments[1], out);
result = this->writeComponentwiseMatrixBinary(callType, lhs, rhs, SpvOpFMul, out);
break;
}
case kAtomicAdd_SpecialIntrinsic:
case kAtomicLoad_SpecialIntrinsic:
case kAtomicStore_SpecialIntrinsic:
result = this->writeAtomicIntrinsic(c, kind, result, out);
break;
case kStorageBarrier_SpecialIntrinsic:
case kWorkgroupBarrier_SpecialIntrinsic: {
// Both barrier types operate in the workgroup execution and memory scope and differ
// only in memory semantics. storageBarrier() is not a device-scope barrier.
SkASSERT(!callType.hasPrecision());
SpvId scopeId =
this->writeOpConstant(*fContext.fTypes.fUInt, (int32_t)SpvScopeWorkgroup);
int32_t memSemMask = (kind == kStorageBarrier_SpecialIntrinsic)
? SpvMemorySemanticsAcquireReleaseMask |
SpvMemorySemanticsUniformMemoryMask
: SpvMemorySemanticsAcquireReleaseMask |
SpvMemorySemanticsWorkgroupMemoryMask;
SpvId memorySemanticsId = this->writeOpConstant(*fContext.fTypes.fUInt, memSemMask);
this->writeInstruction(SpvOpControlBarrier,
scopeId, // execution scope
scopeId, // memory scope
memorySemanticsId,
out);
break;
}
default:
SkUNREACHABLE;
}
return result;
}