• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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