in src/sksl/ir/SkSLVarDeclarations.cpp [96:293]
void VarDeclaration::ErrorCheck(const Context& context,
Position pos,
Position modifiersPosition,
const Layout& layout,
ModifierFlags modifierFlags,
const Type* type,
const Type* baseType,
Variable::Storage storage) {
SkASSERT(type->isArray() ? baseType->matches(type->componentType())
: baseType->matches(*type));
if (baseType->componentType().isOpaque() && !baseType->componentType().isAtomic() &&
storage != Variable::Storage::kGlobal) {
context.fErrors->error(pos, "variables of type '" + baseType->displayName() +
"' must be global");
}
if ((modifierFlags & ModifierFlag::kIn) && baseType->isMatrix()) {
context.fErrors->error(pos, "'in' variables may not have matrix type");
}
if ((modifierFlags & ModifierFlag::kIn) && type->isUnsizedArray()) {
context.fErrors->error(pos, "'in' variables may not have unsized array type");
}
if ((modifierFlags & ModifierFlag::kOut) && type->isUnsizedArray()) {
context.fErrors->error(pos, "'out' variables may not have unsized array type");
}
if ((modifierFlags & ModifierFlag::kIn) && modifierFlags.isUniform()) {
context.fErrors->error(pos, "'in uniform' variables not permitted");
}
if (modifierFlags.isReadOnly() && modifierFlags.isWriteOnly()) {
context.fErrors->error(pos, "'readonly' and 'writeonly' qualifiers cannot be combined");
}
if (modifierFlags.isUniform() && modifierFlags.isBuffer()) {
context.fErrors->error(pos, "'uniform buffer' variables not permitted");
}
if (modifierFlags.isWorkgroup() && (modifierFlags & (ModifierFlag::kIn |
ModifierFlag::kOut))) {
context.fErrors->error(pos, "in / out variables may not be declared workgroup");
}
if (modifierFlags.isUniform()) {
check_valid_uniform_type(pos, baseType, context);
}
if (baseType->isEffectChild() && !modifierFlags.isUniform()) {
context.fErrors->error(pos, "variables of type '" + baseType->displayName() +
"' must be uniform");
}
if (baseType->isEffectChild() && context.fConfig->fKind == ProgramKind::kMeshVertex) {
context.fErrors->error(pos, "effects are not permitted in mesh vertex shaders");
}
if (baseType->isOrContainsAtomic()) {
// An atomic variable (or a struct or an array that contains an atomic member) must be
// either:
// a. Declared as a workgroup-shared variable, OR
// b. Declared as the member of writable storage buffer block (i.e. has no readonly
// restriction).
//
// The checks below will enforce these two rules on all declarations. If the variable is not
// declared with the workgroup modifier, then it must be declared in the interface block
// storage. If this is the declaration for an interface block that contains an atomic
// member, then it must have the `buffer` modifier and no `readonly` modifier.
bool isBlockMember = (storage == Variable::Storage::kInterfaceBlock);
bool isWritableStorageBuffer = modifierFlags.isBuffer() && !modifierFlags.isReadOnly();
if (!modifierFlags.isWorkgroup() &&
!(baseType->isInterfaceBlock() ? isWritableStorageBuffer : isBlockMember)) {
context.fErrors->error(pos, "atomics are only permitted in workgroup variables and "
"writable storage blocks");
}
}
if (layout.fFlags & LayoutFlag::kColor) {
if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
context.fErrors->error(pos, "'layout(color)' is only permitted in runtime effects");
}
if (!modifierFlags.isUniform()) {
context.fErrors->error(pos, "'layout(color)' is only permitted on 'uniform' variables");
}
auto validColorXformType = [](const Type& t) {
return t.isVector() && t.componentType().isFloat() &&
(t.columns() == 3 || t.columns() == 4);
};
if (!validColorXformType(*baseType)) {
context.fErrors->error(pos, "'layout(color)' is not permitted on variables of type '" +
baseType->displayName() + "'");
}
}
ModifierFlags permitted = ModifierFlag::kConst | ModifierFlag::kHighp | ModifierFlag::kMediump |
ModifierFlag::kLowp;
if (storage == Variable::Storage::kGlobal) {
// Uniforms are allowed in all programs
permitted |= ModifierFlag::kUniform;
// No other modifiers are allowed in runtime effects.
if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
if (baseType->isInterfaceBlock()) {
// Interface blocks allow `buffer`.
permitted |= ModifierFlag::kBuffer;
if (modifierFlags.isBuffer()) {
// Only storage blocks allow `readonly` and `writeonly`.
// (`readonly` and `writeonly` textures are converted to separate types via
// applyAccessQualifiers.)
permitted |= ModifierFlag::kReadOnly | ModifierFlag::kWriteOnly;
}
// It is an error for an unsized array to appear anywhere but the last member of a
// "buffer" block.
const auto& fields = baseType->fields();
const int illegalRangeEnd = SkToInt(fields.size()) -
(modifierFlags.isBuffer() ? 1 : 0);
for (int i = 0; i < illegalRangeEnd; ++i) {
if (fields[i].fType->isUnsizedArray()) {
context.fErrors->error(
fields[i].fPosition,
"unsized array must be the last member of a storage block");
}
}
}
if (!baseType->isOpaque()) {
// Only non-opaque types allow `in` and `out`.
permitted |= ModifierFlag::kIn | ModifierFlag::kOut;
}
if (ProgramConfig::IsFragment(context.fConfig->fKind) && baseType->isStruct() &&
!baseType->isInterfaceBlock()) {
// Only structs in fragment shaders allow `pixel_local`.
permitted |= ModifierFlag::kPixelLocal;
}
if (ProgramConfig::IsCompute(context.fConfig->fKind)) {
// Only compute shaders allow `workgroup`.
if (!baseType->isOpaque() || baseType->isAtomic()) {
permitted |= ModifierFlag::kWorkgroup;
}
} else {
// Only vertex/fragment shaders allow `flat` and `noperspective`.
permitted |= ModifierFlag::kFlat | ModifierFlag::kNoPerspective;
}
}
}
LayoutFlags permittedLayoutFlags = LayoutFlag::kAll;
// Pixel format modifiers are required on storage textures, and forbidden on other types.
if (baseType->isStorageTexture()) {
if (!(layout.fFlags & LayoutFlag::kAllPixelFormats)) {
context.fErrors->error(pos, "storage textures must declare a pixel format");
}
} else {
permittedLayoutFlags &= ~LayoutFlag::kAllPixelFormats;
}
// The `texture` and `sampler` modifiers can be present respectively on a texture and sampler or
// simultaneously on a combined image-sampler but they are not permitted on any other type.
switch (baseType->typeKind()) {
case Type::TypeKind::kSampler:
// Both texture and sampler flags are permitted
break;
case Type::TypeKind::kTexture:
permittedLayoutFlags &= ~LayoutFlag::kSampler;
break;
case Type::TypeKind::kSeparateSampler:
permittedLayoutFlags &= ~LayoutFlag::kTexture;
break;
default:
permittedLayoutFlags &= ~(LayoutFlag::kTexture | LayoutFlag::kSampler);
break;
}
// We don't allow 'binding' or 'set' on normal uniform variables, only on textures, samplers,
// and interface blocks (holding uniform variables). They're also only allowed at global scope,
// not on interface block fields (or locals/parameters).
bool permitBindingAndSet = baseType->typeKind() == Type::TypeKind::kSampler ||
baseType->typeKind() == Type::TypeKind::kSeparateSampler ||
baseType->typeKind() == Type::TypeKind::kTexture ||
baseType->isInterfaceBlock();
if (storage != Variable::Storage::kGlobal || (modifierFlags.isUniform() &&
!permitBindingAndSet)) {
permittedLayoutFlags &= ~LayoutFlag::kBinding;
permittedLayoutFlags &= ~LayoutFlag::kSet;
permittedLayoutFlags &= ~LayoutFlag::kAllBackends;
}
if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
// Disallow all layout flags except 'color' in runtime effects
permittedLayoutFlags &= LayoutFlag::kColor;
}
// The `push_constant` flag isn't allowed on in-variables, out-variables, bindings or sets.
if ((layout.fFlags & (LayoutFlag::kSet | LayoutFlag::kBinding)) ||
(modifierFlags & (ModifierFlag::kIn | ModifierFlag::kOut))) {
permittedLayoutFlags &= ~LayoutFlag::kPushConstant;
}
// The `builtin` layout flag is only allowed in modules.
if (!context.fConfig->isBuiltinCode()) {
permittedLayoutFlags &= ~LayoutFlag::kBuiltin;
}
modifierFlags.checkPermittedFlags(context, modifiersPosition, permitted);
layout.checkPermittedLayout(context, modifiersPosition, permittedLayoutFlags);
}