xref: /aosp_15_r20/external/skia/src/sksl/ir/SkSLConstructorScalarCast.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2021 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/SkSLConstructorScalarCast.h"
9 
10 #include "include/core/SkTypes.h"
11 #include "include/private/base/SkTArray.h"
12 #include "src/sksl/SkSLConstantFolder.h"
13 #include "src/sksl/SkSLContext.h"
14 #include "src/sksl/SkSLDefines.h"
15 #include "src/sksl/SkSLErrorReporter.h"
16 #include "src/sksl/ir/SkSLLiteral.h"
17 #include "src/sksl/ir/SkSLType.h"
18 
19 #include <string>
20 
21 namespace SkSL {
22 
Convert(const Context & context,Position pos,const Type & rawType,ExpressionArray args)23 std::unique_ptr<Expression> ConstructorScalarCast::Convert(const Context& context,
24                                                            Position pos,
25                                                            const Type& rawType,
26                                                            ExpressionArray args) {
27     // As you might expect, scalar-cast constructors should only be created with scalar types.
28     const Type& type = rawType.scalarTypeForLiteral();
29     SkASSERT(type.isScalar());
30 
31     if (args.size() != 1) {
32         context.fErrors->error(pos, "invalid arguments to '" + type.displayName() +
33                                     "' constructor, (expected exactly 1 argument, but found " +
34                                     std::to_string(args.size()) + ")");
35         return nullptr;
36     }
37 
38     const Type& argType = args[0]->type();
39     if (!argType.isScalar()) {
40         // Casting a vector-type into its scalar component type is treated as a slice in GLSL.
41         // We don't allow those casts in SkSL; recommend a .x swizzle instead.
42         const char* swizzleHint = "";
43         if (argType.componentType().matches(type)) {
44             if (argType.isVector()) {
45                 swizzleHint = "; use '.x' instead";
46             } else if (argType.isMatrix()) {
47                 swizzleHint = "; use '[0][0]' instead";
48             }
49         }
50 
51         context.fErrors->error(pos,
52                                "'" + argType.displayName() + "' is not a valid parameter to '" +
53                                type.displayName() + "' constructor" + swizzleHint);
54         return nullptr;
55     }
56     if (type.checkForOutOfRangeLiteral(context, *args[0])) {
57         return nullptr;
58     }
59 
60     return ConstructorScalarCast::Make(context, pos, type, std::move(args[0]));
61 }
62 
Make(const Context & context,Position pos,const Type & type,std::unique_ptr<Expression> arg)63 std::unique_ptr<Expression> ConstructorScalarCast::Make(const Context& context,
64                                                         Position pos,
65                                                         const Type& type,
66                                                         std::unique_ptr<Expression> arg) {
67     SkASSERT(type.isScalar());
68     SkASSERT(type.isAllowedInES2(context));
69     SkASSERT(arg->type().isScalar());
70 
71     // No cast required when the types match.
72     if (arg->type().matches(type)) {
73         arg->setPosition(pos);
74         return arg;
75     }
76     // Look up the value of constant variables. This allows constant-expressions like `int(zero)` to
77     // be replaced with a literal zero.
78     arg = ConstantFolder::MakeConstantValueForVariable(pos, std::move(arg));
79 
80     // We can cast scalar literals at compile-time when possible. (If the resulting literal would be
81     // out of range for its type, we report an error and return zero to minimize error cascading.
82     // This can occur when code is inlined, so we can't necessarily catch it during Convert. As
83     // such, it's not safe to return null or assert.)
84     if (arg->is<Literal>()) {
85         double value = arg->as<Literal>().value();
86         if (type.checkForOutOfRangeLiteral(context, value, arg->fPosition)) {
87             value = 0.0;
88         }
89         return Literal::Make(pos, value, &type);
90     }
91 
92     // We allow scalar casts to abstract types `$floatLiteral` or `$intLiteral`. This can be used to
93     // represent various expressions where SkSL still allows type flexibility. For instance, the
94     // expression `float x = myBool ? 1 : 0` is allowed in SkSL despite the apparent type mismatch,
95     // and the resolved type of expression `myBool ? 1 : 0` is actually `$intLiteral`. This
96     // expression could also be rewritten as `$intLiteral(myBool)` to replace a ternary with a cast.
97     //
98     // If we are casting an expression of the form `$intLiteral(...)` or `$floatLiteral(...)`, we
99     // can eliminate the intermediate constructor-cast since it no longer adds value.
100     if (arg->is<ConstructorScalarCast>() && arg->type().isLiteral()) {
101         std::unique_ptr<Expression> inner = std::move(arg->as<ConstructorScalarCast>().argument());
102         return ConstructorScalarCast::Make(context, pos, type, std::move(inner));
103     }
104 
105     return std::make_unique<ConstructorScalarCast>(pos, type, std::move(arg));
106 }
107 
108 }  // namespace SkSL
109