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