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