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
23 namespace SkSL {
24
convert_compound_constructor(const Context & context,int line,const Type & type,ExpressionArray args)25 static 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
Convert(const Context & context,int line,const Type & type,ExpressionArray args)150 std::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
getConstantValue(int n) const176 skstd::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
compareConstant(const Expression & other) const190 Expression::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
asAnyConstructor()216 AnyConstructor& Expression::asAnyConstructor() {
217 SkASSERT(this->isAnyConstructor());
218 return static_cast<AnyConstructor&>(*this);
219 }
220
asAnyConstructor() const221 const AnyConstructor& Expression::asAnyConstructor() const {
222 SkASSERT(this->isAnyConstructor());
223 return static_cast<const AnyConstructor&>(*this);
224 }
225
226 } // namespace SkSL
227