1/* 2 * Copyright 2020 Google LLC. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8#include "src/sksl/ir/SkSLConstructor.h" 9 10#include "include/sksl/SkSLErrorReporter.h" 11#include "src/sksl/ir/SkSLConstructorArray.h" 12#include "src/sksl/ir/SkSLConstructorCompound.h" 13#include "src/sksl/ir/SkSLConstructorCompoundCast.h" 14#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h" 15#include "src/sksl/ir/SkSLConstructorMatrixResize.h" 16#include "src/sksl/ir/SkSLConstructorScalarCast.h" 17#include "src/sksl/ir/SkSLConstructorSplat.h" 18#include "src/sksl/ir/SkSLConstructorStruct.h" 19#include "src/sksl/ir/SkSLLiteral.h" 20#include "src/sksl/ir/SkSLPrefixExpression.h" 21#include "src/sksl/ir/SkSLType.h" 22 23namespace SkSL { 24 25static std::unique_ptr<Expression> convert_compound_constructor(const Context& context, 26 int line, 27 const Type& type, 28 ExpressionArray args) { 29 SkASSERT(type.isVector() || type.isMatrix()); 30 31 // The meaning of a compound constructor containing a single argument varies significantly in 32 // GLSL/SkSL, depending on the argument type. 33 if (args.size() == 1) { 34 std::unique_ptr<Expression>& argument = args.front(); 35 if (type.isVector() && argument->type().isVector() && 36 argument->type().componentType() == type.componentType() && 37 argument->type().slotCount() > type.slotCount()) { 38 // Casting a vector-type into a smaller matching vector-type is a slice in GLSL. 39 // We don't allow those casts in SkSL; recommend a swizzle instead. 40 // Only `.xy` and `.xyz` are valid recommendations here, because `.x` would imply a 41 // scalar(vector) cast, and nothing has more slots than `.xyzw`. 42 const char* swizzleHint; 43 switch (type.slotCount()) { 44 case 2: swizzleHint = "; use '.xy' instead"; break; 45 case 3: swizzleHint = "; use '.xyz' instead"; break; 46 default: swizzleHint = ""; SkDEBUGFAIL("unexpected slicing cast"); break; 47 } 48 49 context.fErrors->error(line, "'" + argument->type().displayName() + 50 "' is not a valid parameter to '" + type.displayName() + 51 "' constructor" + swizzleHint); 52 return nullptr; 53 } 54 55 if (argument->type().isScalar()) { 56 // A constructor containing a single scalar is a splat (for vectors) or diagonal matrix 57 // (for matrices). It's legal regardless of the scalar's type, so synthesize an explicit 58 // conversion to the proper type. (This cast is a no-op if it's unnecessary; it can fail 59 // if we're casting a literal that exceeds the limits of the type.) 60 std::unique_ptr<Expression> typecast = ConstructorScalarCast::Convert( 61 context, line, type.componentType(), std::move(args)); 62 if (!typecast) { 63 return nullptr; 64 } 65 66 // Matrix-from-scalar creates a diagonal matrix; vector-from-scalar creates a splat. 67 return type.isMatrix() 68 ? ConstructorDiagonalMatrix::Make(context, line, type, std::move(typecast)) 69 : ConstructorSplat::Make(context, line, type, std::move(typecast)); 70 } else if (argument->type().isVector()) { 71 // A vector constructor containing a single vector with the same number of columns is a 72 // cast (e.g. float3 -> int3). 73 if (type.isVector() && argument->type().columns() == type.columns()) { 74 return ConstructorCompoundCast::Make(context, line, type, std::move(argument)); 75 } 76 } else if (argument->type().isMatrix()) { 77 // A matrix constructor containing a single matrix can be a resize, typecast, or both. 78 // GLSL lumps these into one category, but internally SkSL keeps them distinct. 79 if (type.isMatrix()) { 80 // First, handle type conversion. If the component types differ, synthesize the 81 // destination type with the argument's rows/columns. (This will be a no-op if it's 82 // already the right type.) 83 const Type& typecastType = type.componentType().toCompound( 84 context, 85 argument->type().columns(), 86 argument->type().rows()); 87 argument = ConstructorCompoundCast::Make(context, line, typecastType, 88 std::move(argument)); 89 90 // Casting a matrix type into another matrix type is a resize. 91 return ConstructorMatrixResize::Make(context, line, type, 92 std::move(argument)); 93 } 94 95 // A vector constructor containing a single matrix can be compound construction if the 96 // matrix is 2x2 and the vector is 4-slot. 97 if (type.isVector() && type.columns() == 4 && argument->type().slotCount() == 4) { 98 // Casting a 2x2 matrix to a vector is a form of compound construction. 99 // First, reshape the matrix into a 4-slot vector of the same type. 100 const Type& vectorType = argument->type().componentType().toCompound(context, 101 /*columns=*/4, 102 /*rows=*/1); 103 std::unique_ptr<Expression> vecCtor = 104 ConstructorCompound::Make(context, line, vectorType, std::move(args)); 105 106 // Then, add a typecast to the result expression to ensure the types match. 107 // This will be a no-op if no typecasting is needed. 108 return ConstructorCompoundCast::Make(context, line, type, std::move(vecCtor)); 109 } 110 } 111 } 112 113 // For more complex cases, we walk the argument list and fix up the arguments as needed. 114 int expected = type.rows() * type.columns(); 115 int actual = 0; 116 for (std::unique_ptr<Expression>& arg : args) { 117 if (!arg->type().isScalar() && !arg->type().isVector()) { 118 context.fErrors->error(line, "'" + arg->type().displayName() + 119 "' is not a valid parameter to '" + type.displayName() + 120 "' constructor"); 121 return nullptr; 122 } 123 124 // Rely on Constructor::Convert to force this subexpression to the proper type. If it's a 125 // literal, this will make sure it's the right type of literal. If an expression of matching 126 // type, the expression will be returned as-is. If it's an expression of mismatched type, 127 // this adds a cast. 128 int ctorLine = arg->fLine; 129 const Type& ctorType = type.componentType().toCompound(context, arg->type().columns(), 130 /*rows=*/1); 131 ExpressionArray ctorArg; 132 ctorArg.push_back(std::move(arg)); 133 arg = Constructor::Convert(context, ctorLine, ctorType, std::move(ctorArg)); 134 if (!arg) { 135 return nullptr; 136 } 137 actual += ctorType.columns(); 138 } 139 140 if (actual != expected) { 141 context.fErrors->error(line, "invalid arguments to '" + type.displayName() + 142 "' constructor (expected " + to_string(expected) + 143 " scalars, but found " + to_string(actual) + ")"); 144 return nullptr; 145 } 146 147 return ConstructorCompound::Make(context, line, type, std::move(args)); 148} 149 150std::unique_ptr<Expression> Constructor::Convert(const Context& context, 151 int line, 152 const Type& type, 153 ExpressionArray args) { 154 if (args.size() == 1 && args[0]->type() == type && !type.componentType().isOpaque()) { 155 // Don't generate redundant casts; if the expression is already of the correct type, just 156 // return it as-is. 157 return std::move(args[0]); 158 } 159 if (type.isScalar()) { 160 return ConstructorScalarCast::Convert(context, line, type, std::move(args)); 161 } 162 if (type.isVector() || type.isMatrix()) { 163 return convert_compound_constructor(context, line, type, std::move(args)); 164 } 165 if (type.isArray() && type.columns() > 0) { 166 return ConstructorArray::Convert(context, line, type, std::move(args)); 167 } 168 if (type.isStruct() && type.fields().size() > 0) { 169 return ConstructorStruct::Convert(context, line, type, std::move(args)); 170 } 171 172 context.fErrors->error(line, "cannot construct '" + type.displayName() + "'"); 173 return nullptr; 174} 175 176skstd::optional<double> AnyConstructor::getConstantValue(int n) const { 177 SkASSERT(n >= 0 && n < (int)this->type().slotCount()); 178 for (const std::unique_ptr<Expression>& arg : this->argumentSpan()) { 179 int argSlots = arg->type().slotCount(); 180 if (n < argSlots) { 181 return arg->getConstantValue(n); 182 } 183 n -= argSlots; 184 } 185 186 SkDEBUGFAIL("argument-list slot count doesn't match constructor-type slot count"); 187 return skstd::nullopt; 188} 189 190Expression::ComparisonResult AnyConstructor::compareConstant(const Expression& other) const { 191 SkASSERT(this->type().slotCount() == other.type().slotCount()); 192 193 if (!other.supportsConstantValues()) { 194 return ComparisonResult::kUnknown; 195 } 196 197 int exprs = this->type().slotCount(); 198 for (int n = 0; n < exprs; ++n) { 199 // Get the n'th subexpression from each side. If either one is null, return "unknown." 200 skstd::optional<double> left = this->getConstantValue(n); 201 if (!left.has_value()) { 202 return ComparisonResult::kUnknown; 203 } 204 skstd::optional<double> right = other.getConstantValue(n); 205 if (!right.has_value()) { 206 return ComparisonResult::kUnknown; 207 } 208 // Both sides are known and can be compared for equality directly. 209 if (*left != *right) { 210 return ComparisonResult::kNotEqual; 211 } 212 } 213 return ComparisonResult::kEqual; 214} 215 216AnyConstructor& Expression::asAnyConstructor() { 217 SkASSERT(this->isAnyConstructor()); 218 return static_cast<AnyConstructor&>(*this); 219} 220 221const AnyConstructor& Expression::asAnyConstructor() const { 222 SkASSERT(this->isAnyConstructor()); 223 return static_cast<const AnyConstructor&>(*this); 224} 225 226} // namespace SkSL 227