| /* |
| * Copyright 2021 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/SkSLConstantFolder.h" |
| #include "src/sksl/ir/SkSLConstructor.h" |
| #include "src/sksl/ir/SkSLConstructorScalarCast.h" |
| #include "src/sksl/ir/SkSLConstructorSplat.h" |
| #include "src/sksl/ir/SkSLSwizzle.h" |
| |
| namespace SkSL { |
| |
| std::unique_ptr<Expression> Swizzle::Convert(const Context& context, |
| std::unique_ptr<Expression> base, |
| ComponentArray inComponents) { |
| const int offset = base->fOffset; |
| const Type& baseType = base->type(); |
| |
| // The IRGenerator is responsible for enforcing these invariants. |
| SkASSERTF(baseType.isVector() || baseType.isScalar(), |
| "cannot swizzle type '%s'", baseType.description().c_str()); |
| SkASSERT(inComponents.count() >= 1 && inComponents.count() <= 4); |
| |
| ComponentArray maskComponents; |
| for (int8_t component : inComponents) { |
| switch (component) { |
| case SwizzleComponent::ZERO: |
| case SwizzleComponent::ONE: |
| // Skip over constant fields for now. |
| break; |
| case SwizzleComponent::X: |
| maskComponents.push_back(SwizzleComponent::X); |
| break; |
| case SwizzleComponent::Y: |
| if (baseType.columns() >= 2) { |
| maskComponents.push_back(SwizzleComponent::Y); |
| break; |
| } |
| [[fallthrough]]; |
| case SwizzleComponent::Z: |
| if (baseType.columns() >= 3) { |
| maskComponents.push_back(SwizzleComponent::Z); |
| break; |
| } |
| [[fallthrough]]; |
| case SwizzleComponent::W: |
| if (baseType.columns() >= 4) { |
| maskComponents.push_back(SwizzleComponent::W); |
| break; |
| } |
| [[fallthrough]]; |
| default: |
| SkDEBUGFAILF("invalid swizzle component %d", component); |
| return nullptr; |
| } |
| } |
| |
| // First, we need a vector expression that is the non-constant portion of the swizzle, packed: |
| // scalar.xxx -> type3(scalar) |
| // scalar.x0x0 -> type2(scalar) |
| // vector.zyx -> vector.zyx |
| // vector.x0y0 -> vector.xy |
| std::unique_ptr<Expression> expr = Swizzle::Make(context, std::move(base), maskComponents); |
| |
| // If we have processed the entire swizzle, we're done. |
| if (maskComponents.count() == inComponents.count()) { |
| return expr; |
| } |
| |
| // Now we create a constructor that has the correct number of elements for the final swizzle, |
| // with all fields at the start. It's not finished yet; constants we need will be added below. |
| // scalar.x0x0 -> type4(type2(x), ...) |
| // vector.y111 -> type4(vector.y, ...) |
| // vector.z10x -> type4(vector.zx, ...) |
| // |
| // The constructor will have at most three arguments: { base expr, constant 0, constant 1 } |
| ExpressionArray constructorArgs; |
| constructorArgs.reserve_back(3); |
| constructorArgs.push_back(std::move(expr)); |
| |
| // Apply another swizzle to shuffle the constants into the correct place. Any constant values we |
| // need are also tacked on to the end of the constructor. |
| // scalar.x0x0 -> type4(type2(x), 0).xyxy |
| // vector.y111 -> type4(vector.y, 1).xyyy |
| // vector.z10x -> type4(vector.zx, 1, 0).xzwy |
| const Type* numberType = &baseType.componentType(); |
| ComponentArray swizzleComponents; |
| int maskFieldIdx = 0; |
| int constantFieldIdx = maskComponents.size(); |
| int constantZeroIdx = -1, constantOneIdx = -1; |
| |
| for (int i = 0; i < inComponents.count(); i++) { |
| switch (inComponents[i]) { |
| case SwizzleComponent::ZERO: |
| if (constantZeroIdx == -1) { |
| // Synthesize a 'type(0)' argument at the end of the constructor. |
| constructorArgs.push_back(ConstructorScalarCast::Make( |
| context, offset, *numberType, |
| IntLiteral::Make(context, offset, /*value=*/0))); |
| constantZeroIdx = constantFieldIdx++; |
| } |
| swizzleComponents.push_back(constantZeroIdx); |
| break; |
| case SwizzleComponent::ONE: |
| if (constantOneIdx == -1) { |
| // Synthesize a 'type(1)' argument at the end of the constructor. |
| constructorArgs.push_back(ConstructorScalarCast::Make( |
| context, offset, *numberType, |
| IntLiteral::Make(context, offset, /*value=*/1))); |
| constantOneIdx = constantFieldIdx++; |
| } |
| swizzleComponents.push_back(constantOneIdx); |
| break; |
| default: |
| // The non-constant fields are already in the expected order. |
| swizzleComponents.push_back(maskFieldIdx++); |
| break; |
| } |
| } |
| |
| expr = Constructor::Convert(context, offset, |
| numberType->toCompound(context, constantFieldIdx, /*rows=*/1), |
| std::move(constructorArgs)); |
| if (!expr) { |
| return nullptr; |
| } |
| |
| return Swizzle::Make(context, std::move(expr), swizzleComponents); |
| } |
| |
| std::unique_ptr<Expression> Swizzle::Make(const Context& context, |
| std::unique_ptr<Expression> expr, |
| ComponentArray components) { |
| const Type& exprType = expr->type(); |
| SkASSERTF(exprType.isVector() || exprType.isScalar(), |
| "cannot swizzle type '%s'", exprType.description().c_str()); |
| SkASSERT(components.count() >= 1 && components.count() <= 4); |
| |
| // Confirm that the component array only contains X/Y/Z/W. (Call MakeWith01 if you want support |
| // for ZERO and ONE. Once initial IR generation is complete, no swizzles should have zeros or |
| // ones in them.) |
| SkASSERT(std::all_of(components.begin(), components.end(), [](int8_t component) { |
| return component >= SwizzleComponent::X && |
| component <= SwizzleComponent::W; |
| })); |
| |
| // SkSL supports splatting a scalar via `scalar.xxxx`, but not all versions of GLSL allow this. |
| // Replace swizzles with equivalent splat constructors (`scalar.xxx` --> `half3(value)`). |
| if (exprType.isScalar()) { |
| int offset = expr->fOffset; |
| return ConstructorSplat::Make(context, offset, |
| exprType.toCompound(context, components.size(), /*rows=*/1), |
| std::move(expr)); |
| } |
| |
| if (context.fConfig->fSettings.fOptimize) { |
| // Detect identity swizzles like `color.rgba` and return the base-expression as-is. |
| if (components.count() == exprType.columns()) { |
| bool identity = true; |
| for (int i = 0; i < components.count(); ++i) { |
| if (components[i] != i) { |
| identity = false; |
| break; |
| } |
| } |
| if (identity) { |
| return expr; |
| } |
| } |
| |
| // Optimize swizzles of swizzles, e.g. replace `foo.argb.rggg` with `foo.arrr`. |
| if (expr->is<Swizzle>()) { |
| Swizzle& base = expr->as<Swizzle>(); |
| ComponentArray combined; |
| for (int8_t c : components) { |
| combined.push_back(base.components()[c]); |
| } |
| |
| // It may actually be possible to further simplify this swizzle. Go again. |
| // (e.g. `color.abgr.abgr` --> `color.rgba` --> `color`.) |
| return Swizzle::Make(context, std::move(base.base()), combined); |
| } |
| |
| // If we are swizzling a constant expression, we can use its value instead here (so that |
| // swizzles like `colorWhite.x` can be simplified to `1`). |
| const Expression* value = ConstantFolder::GetConstantValueForVariable(*expr); |
| |
| // `half4(scalar).zyy` can be optimized to `half3(scalar)`, and `half3(scalar).y` can be |
| // optimized to just `scalar`. The swizzle components don't actually matter, as every field |
| // in a splat constructor holds the same value. |
| if (value->is<ConstructorSplat>()) { |
| const ConstructorSplat& splat = value->as<ConstructorSplat>(); |
| return ConstructorSplat::Make( |
| context, splat.fOffset, |
| splat.type().componentType().toCompound(context, components.size(), /*rows=*/1), |
| splat.argument()->clone()); |
| } |
| |
| // Optimize swizzles of constructors. |
| if (value->isAnyConstructor()) { |
| const AnyConstructor& base = value->asAnyConstructor(); |
| auto baseArguments = base.argumentSpan(); |
| std::unique_ptr<Expression> replacement; |
| const Type& componentType = exprType.componentType(); |
| int swizzleSize = components.size(); |
| |
| // Swizzles can duplicate some elements and discard others, e.g. |
| // `half4(1, 2, 3, 4).xxz` --> `half3(1, 1, 3)`. However, there are constraints: |
| // - Expressions with side effects need to occur exactly once, even if they |
| // would otherwise be swizzle-eliminated |
| // - Non-trivial expressions should not be repeated, but elimination is OK. |
| // |
| // Look up the argument for the constructor at each index. This is typically simple |
| // but for weird cases like `half4(bar.yz, half2(foo))`, it can be harder than it |
| // seems. This example would result in: |
| // argMap[0] = {.fArgIndex = 0, .fComponent = 0} (bar.yz .x) |
| // argMap[1] = {.fArgIndex = 0, .fComponent = 1} (bar.yz .y) |
| // argMap[2] = {.fArgIndex = 1, .fComponent = 0} (half2(foo) .x) |
| // argMap[3] = {.fArgIndex = 1, .fComponent = 1} (half2(foo) .y) |
| struct ConstructorArgMap { |
| int8_t fArgIndex; |
| int8_t fComponent; |
| }; |
| |
| int numConstructorArgs = base.type().columns(); |
| ConstructorArgMap argMap[4] = {}; |
| int writeIdx = 0; |
| for (int argIdx = 0; argIdx < (int)baseArguments.size(); ++argIdx) { |
| const Expression& arg = *baseArguments[argIdx]; |
| int argWidth = arg.type().columns(); |
| for (int componentIdx = 0; componentIdx < argWidth; ++componentIdx) { |
| argMap[writeIdx].fArgIndex = argIdx; |
| argMap[writeIdx].fComponent = componentIdx; |
| ++writeIdx; |
| } |
| } |
| SkASSERT(writeIdx == numConstructorArgs); |
| |
| // Count up the number of times each constructor argument is used by the |
| // swizzle. |
| // `half4(bar.yz, half2(foo)).xwxy` -> { 3, 1 } |
| // - bar.yz is referenced 3 times, by `.x_xy` |
| // - half(foo) is referenced 1 time, by `._w__` |
| int8_t exprUsed[4] = {}; |
| for (int8_t c : components) { |
| exprUsed[argMap[c].fArgIndex]++; |
| } |
| |
| bool safeToOptimize = true; |
| for (int index = 0; index < numConstructorArgs; ++index) { |
| int8_t constructorArgIndex = argMap[index].fArgIndex; |
| const Expression& baseArg = *baseArguments[constructorArgIndex]; |
| |
| // Check that non-trivial expressions are not swizzled in more than once. |
| if (exprUsed[constructorArgIndex] > 1 && !Analysis::IsTrivialExpression(baseArg)) { |
| safeToOptimize = false; |
| break; |
| } |
| // Check that side-effect-bearing expressions are swizzled in exactly once. |
| if (exprUsed[constructorArgIndex] != 1 && baseArg.hasSideEffects()) { |
| safeToOptimize = false; |
| break; |
| } |
| } |
| |
| if (safeToOptimize) { |
| struct ReorderedArgument { |
| int8_t fArgIndex; |
| ComponentArray fComponents; |
| }; |
| SkSTArray<4, ReorderedArgument> reorderedArgs; |
| for (int8_t c : components) { |
| const ConstructorArgMap& argument = argMap[c]; |
| const Expression& baseArg = *baseArguments[argument.fArgIndex]; |
| |
| if (baseArg.type().isScalar()) { |
| // This argument is a scalar; add it to the list as-is. |
| SkASSERT(argument.fComponent == 0); |
| reorderedArgs.push_back({argument.fArgIndex, |
| ComponentArray{}}); |
| } else { |
| // This argument is a component from a vector. |
| SkASSERT(argument.fComponent < baseArg.type().columns()); |
| if (reorderedArgs.empty() || |
| reorderedArgs.back().fArgIndex != argument.fArgIndex) { |
| // This can't be combined with the previous argument. Add a new one. |
| reorderedArgs.push_back({argument.fArgIndex, |
| ComponentArray{argument.fComponent}}); |
| } else { |
| // Since we know this argument uses components, it should already |
| // have at least one component set. |
| SkASSERT(!reorderedArgs.back().fComponents.empty()); |
| // Build up the current argument with one more component. |
| reorderedArgs.back().fComponents.push_back(argument.fComponent); |
| } |
| } |
| } |
| |
| // Convert our reordered argument list to an actual array of expressions, with |
| // the new order and any new inner swizzles that need to be applied. |
| ExpressionArray newArgs; |
| newArgs.reserve_back(swizzleSize); |
| for (const ReorderedArgument& reorderedArg : reorderedArgs) { |
| std::unique_ptr<Expression> newArg = |
| baseArguments[reorderedArg.fArgIndex]->clone(); |
| |
| if (reorderedArg.fComponents.empty()) { |
| newArgs.push_back(std::move(newArg)); |
| } else { |
| newArgs.push_back(Swizzle::Make(context, std::move(newArg), |
| reorderedArg.fComponents)); |
| } |
| } |
| |
| // Wrap the new argument list in a constructor. |
| auto ctor = Constructor::Convert( |
| context, base.fOffset, |
| componentType.toCompound(context, swizzleSize, /*rows=*/1), |
| std::move(newArgs)); |
| SkASSERT(ctor); |
| return ctor; |
| } |
| } |
| } |
| |
| // The swizzle could not be simplified, so apply the requested swizzle to the base expression. |
| return std::make_unique<Swizzle>(context, std::move(expr), components); |
| } |
| |
| } // namespace SkSL |