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