| /* |
| * Copyright 2020 Google LLC. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "src/sksl/ir/SkSLConstructor.h" |
| |
| #include "include/core/SkTypes.h" |
| #include "include/private/SkSLString.h" |
| #include "include/private/SkTArray.h" |
| #include "include/sksl/SkSLErrorReporter.h" |
| #include "include/sksl/SkSLOperator.h" |
| #include "src/sksl/SkSLContext.h" |
| #include "src/sksl/ir/SkSLConstructorArray.h" |
| #include "src/sksl/ir/SkSLConstructorCompound.h" |
| #include "src/sksl/ir/SkSLConstructorCompoundCast.h" |
| #include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h" |
| #include "src/sksl/ir/SkSLConstructorMatrixResize.h" |
| #include "src/sksl/ir/SkSLConstructorScalarCast.h" |
| #include "src/sksl/ir/SkSLConstructorSplat.h" |
| #include "src/sksl/ir/SkSLConstructorStruct.h" |
| #include "src/sksl/ir/SkSLType.h" |
| |
| #include <vector> |
| |
| namespace SkSL { |
| |
| static std::unique_ptr<Expression> convert_compound_constructor(const Context& context, |
| Position pos, |
| const Type& type, |
| ExpressionArray args) { |
| SkASSERT(type.isVector() || type.isMatrix()); |
| |
| // The meaning of a compound constructor containing a single argument varies significantly in |
| // GLSL/SkSL, depending on the argument type. |
| if (args.size() == 1) { |
| std::unique_ptr<Expression>& argument = args.front(); |
| if (type.isVector() && argument->type().isVector() && |
| argument->type().componentType().matches(type.componentType()) && |
| argument->type().slotCount() > type.slotCount()) { |
| // Casting a vector-type into a smaller matching vector-type is a slice in GLSL. |
| // We don't allow those casts in SkSL; recommend a swizzle instead. |
| // Only `.xy` and `.xyz` are valid recommendations here, because `.x` would imply a |
| // scalar(vector) cast, and nothing has more slots than `.xyzw`. |
| const char* swizzleHint; |
| switch (type.slotCount()) { |
| case 2: swizzleHint = "; use '.xy' instead"; break; |
| case 3: swizzleHint = "; use '.xyz' instead"; break; |
| default: swizzleHint = ""; SkDEBUGFAIL("unexpected slicing cast"); break; |
| } |
| |
| context.fErrors->error(pos, "'" + argument->type().displayName() + |
| "' is not a valid parameter to '" + type.displayName() + "' constructor" + |
| swizzleHint); |
| return nullptr; |
| } |
| |
| if (argument->type().isScalar()) { |
| // A constructor containing a single scalar is a splat (for vectors) or diagonal matrix |
| // (for matrices). It's legal regardless of the scalar's type, so synthesize an explicit |
| // conversion to the proper type. (This cast is a no-op if it's unnecessary; it can fail |
| // if we're casting a literal that exceeds the limits of the type.) |
| std::unique_ptr<Expression> typecast = ConstructorScalarCast::Convert( |
| context, pos, type.componentType(), std::move(args)); |
| if (!typecast) { |
| return nullptr; |
| } |
| |
| // Matrix-from-scalar creates a diagonal matrix; vector-from-scalar creates a splat. |
| return type.isMatrix() |
| ? ConstructorDiagonalMatrix::Make(context, pos, type, std::move(typecast)) |
| : ConstructorSplat::Make(context, pos, type, std::move(typecast)); |
| } else if (argument->type().isVector()) { |
| // A vector constructor containing a single vector with the same number of columns is a |
| // cast (e.g. float3 -> int3). |
| if (type.isVector() && argument->type().columns() == type.columns()) { |
| return ConstructorCompoundCast::Make(context, pos, type, std::move(argument)); |
| } |
| } else if (argument->type().isMatrix()) { |
| // A matrix constructor containing a single matrix can be a resize, typecast, or both. |
| // GLSL lumps these into one category, but internally SkSL keeps them distinct. |
| if (type.isMatrix()) { |
| // First, handle type conversion. If the component types differ, synthesize the |
| // destination type with the argument's rows/columns. (This will be a no-op if it's |
| // already the right type.) |
| const Type& typecastType = type.componentType().toCompound( |
| context, |
| argument->type().columns(), |
| argument->type().rows()); |
| argument = ConstructorCompoundCast::Make(context, pos, typecastType, |
| std::move(argument)); |
| |
| // Casting a matrix type into another matrix type is a resize. |
| return ConstructorMatrixResize::Make(context, pos, type, |
| std::move(argument)); |
| } |
| |
| // A vector constructor containing a single matrix can be compound construction if the |
| // matrix is 2x2 and the vector is 4-slot. |
| if (type.isVector() && type.columns() == 4 && argument->type().slotCount() == 4) { |
| // Casting a 2x2 matrix to a vector is a form of compound construction. |
| // First, reshape the matrix into a 4-slot vector of the same type. |
| const Type& vectorType = argument->type().componentType().toCompound(context, |
| /*columns=*/4, |
| /*rows=*/1); |
| std::unique_ptr<Expression> vecCtor = |
| ConstructorCompound::Make(context, pos, vectorType, std::move(args)); |
| |
| // Then, add a typecast to the result expression to ensure the types match. |
| // This will be a no-op if no typecasting is needed. |
| return ConstructorCompoundCast::Make(context, pos, type, std::move(vecCtor)); |
| } |
| } |
| } |
| |
| // For more complex cases, we walk the argument list and fix up the arguments as needed. |
| int expected = type.rows() * type.columns(); |
| int actual = 0; |
| for (std::unique_ptr<Expression>& arg : args) { |
| if (!arg->type().isScalar() && !arg->type().isVector()) { |
| context.fErrors->error(pos, "'" + arg->type().displayName() + |
| "' is not a valid parameter to '" + type.displayName() + "' constructor"); |
| return nullptr; |
| } |
| |
| // Rely on Constructor::Convert to force this subexpression to the proper type. If it's a |
| // literal, this will make sure it's the right type of literal. If an expression of matching |
| // type, the expression will be returned as-is. If it's an expression of mismatched type, |
| // this adds a cast. |
| const Type& ctorType = type.componentType().toCompound(context, arg->type().columns(), |
| /*rows=*/1); |
| ExpressionArray ctorArg; |
| ctorArg.push_back(std::move(arg)); |
| arg = Constructor::Convert(context, pos, ctorType, std::move(ctorArg)); |
| if (!arg) { |
| return nullptr; |
| } |
| actual += ctorType.columns(); |
| } |
| |
| if (actual != expected) { |
| context.fErrors->error(pos, "invalid arguments to '" + type.displayName() + |
| "' constructor (expected " + std::to_string(expected) + |
| " scalars, but found " + std::to_string(actual) + ")"); |
| return nullptr; |
| } |
| |
| return ConstructorCompound::Make(context, pos, type, std::move(args)); |
| } |
| |
| std::unique_ptr<Expression> Constructor::Convert(const Context& context, |
| Position pos, |
| const Type& type, |
| ExpressionArray args) { |
| if (args.size() == 1 && args[0]->type().matches(type) && !type.componentType().isOpaque()) { |
| // Don't generate redundant casts; if the expression is already of the correct type, just |
| // return it as-is. |
| args[0]->fPosition = pos; |
| return std::move(args[0]); |
| } |
| if (type.isScalar()) { |
| return ConstructorScalarCast::Convert(context, pos, type, std::move(args)); |
| } |
| if (type.isVector() || type.isMatrix()) { |
| return convert_compound_constructor(context, pos, type, std::move(args)); |
| } |
| if (type.isArray() && type.columns() > 0) { |
| return ConstructorArray::Convert(context, pos, type, std::move(args)); |
| } |
| if (type.isStruct() && type.fields().size() > 0) { |
| return ConstructorStruct::Convert(context, pos, type, std::move(args)); |
| } |
| |
| context.fErrors->error(pos, "cannot construct '" + type.displayName() + "'"); |
| return nullptr; |
| } |
| |
| std::optional<double> AnyConstructor::getConstantValue(int n) const { |
| SkASSERT(n >= 0 && n < (int)this->type().slotCount()); |
| for (const std::unique_ptr<Expression>& arg : this->argumentSpan()) { |
| int argSlots = arg->type().slotCount(); |
| if (n < argSlots) { |
| return arg->getConstantValue(n); |
| } |
| n -= argSlots; |
| } |
| |
| SkDEBUGFAIL("argument-list slot count doesn't match constructor-type slot count"); |
| return std::nullopt; |
| } |
| |
| Expression::ComparisonResult AnyConstructor::compareConstant(const Expression& other) const { |
| SkASSERT(this->type().slotCount() == other.type().slotCount()); |
| |
| if (!other.supportsConstantValues()) { |
| return ComparisonResult::kUnknown; |
| } |
| |
| int exprs = this->type().slotCount(); |
| for (int n = 0; n < exprs; ++n) { |
| // Get the n'th subexpression from each side. If either one is null, return "unknown." |
| std::optional<double> left = this->getConstantValue(n); |
| if (!left.has_value()) { |
| return ComparisonResult::kUnknown; |
| } |
| std::optional<double> right = other.getConstantValue(n); |
| if (!right.has_value()) { |
| return ComparisonResult::kUnknown; |
| } |
| // Both sides are known and can be compared for equality directly. |
| if (*left != *right) { |
| return ComparisonResult::kNotEqual; |
| } |
| } |
| return ComparisonResult::kEqual; |
| } |
| |
| AnyConstructor& Expression::asAnyConstructor() { |
| SkASSERT(this->isAnyConstructor()); |
| return static_cast<AnyConstructor&>(*this); |
| } |
| |
| const AnyConstructor& Expression::asAnyConstructor() const { |
| SkASSERT(this->isAnyConstructor()); |
| return static_cast<const AnyConstructor&>(*this); |
| } |
| |
| std::string AnyConstructor::description(OperatorPrecedence) const { |
| std::string result = this->type().description() + "("; |
| auto separator = SkSL::String::Separator(); |
| for (const std::unique_ptr<Expression>& arg : this->argumentSpan()) { |
| result += separator(); |
| result += arg->description(OperatorPrecedence::kSequence); |
| } |
| result.push_back(')'); |
| return result; |
| } |
| |
| } // namespace SkSL |