Source code
Revision control
Copy as Markdown
Other Tools
//
// Copyright 2025 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// RewriteMultielemntSwizzleAssignment: WGSL does not support assignment to multielement swizzles.
// This splits them into mutliple assignments to single-element swizzles.
//
#include "compiler/translator/tree_ops/wgsl/RewriteMultielementSwizzleAssignment.h"
#include "common/log_utils.h"
#include "compiler/translator/BaseTypes.h"
#include "compiler/translator/IntermNode.h"
#include "compiler/translator/Operator_autogen.h"
#include "compiler/translator/SymbolTable.h"
#include "compiler/translator/tree_util/DriverUniform.h"
#include "compiler/translator/tree_util/IntermNode_util.h"
#include "compiler/translator/tree_util/IntermTraverse.h"
namespace sh
{
namespace
{
// Splits multielement swizzles into single-element swizzles.
class MultielementSwizzleAssignmentTraverser : public TIntermTraverser
{
public:
MultielementSwizzleAssignmentTraverser(TCompiler *compiler)
: TIntermTraverser(true, false, false), mCompiler(compiler)
{}
bool visitBinary(Visit visit, TIntermBinary *node) override;
bool visitUnary(Visit visit, TIntermUnary *node) override;
private:
TCompiler *mCompiler;
};
bool MultielementSwizzleAssignmentTraverser::visitUnary(Visit vist, TIntermUnary *unaryNode)
{
if (!IsMultielementSwizzleAssignment(unaryNode->getOp(), unaryNode->getOperand()))
{
return true;
}
// TODO(anglebug.com/42267100): increments and decrements should be handled by generated WGSL
// functions, and this can just be deleted.
switch (unaryNode->getOp())
{
case EOpPostIncrement:
case EOpPostDecrement:
case EOpPreIncrement:
case EOpPreDecrement:
UNIMPLEMENTED();
break;
default:
break;
}
return true;
}
bool MultielementSwizzleAssignmentTraverser::visitBinary(Visit visit, TIntermBinary *parentBinNode)
{
ASSERT(visit == Visit::PreVisit);
if (!IsMultielementSwizzleAssignment(parentBinNode->getOp(), parentBinNode->getLeft()))
{
return true;
}
if (!CanRewriteMultiElementSwizzleAssignmentEasily(parentBinNode, getParentNode()))
{
UNREACHABLE();
return false;
}
TIntermSequence insertionsBefore;
TIntermSwizzle *leftSwizzleNode = parentBinNode->getLeft()->getAsSwizzleNode();
// Store the RHS in a temp in case of side effects, since it will be duplicated.
TIntermDeclaration *rhsTempDeclaration;
TVariable *rhsTempVariable =
DeclareTempVariable(&mCompiler->getSymbolTable(), parentBinNode->getRight()->deepCopy(),
EvqTemporary, &rhsTempDeclaration);
insertionsBefore.push_back(rhsTempDeclaration);
TIntermSequence singleElementSwizzleAssignments;
for (uint32_t i = 0; i < leftSwizzleNode->getSwizzleOffsets().size(); i++)
{
// CanRewriteMultiElementSwizzleAssignmentEasily() should have filtered these cases out.
ASSERT(!leftSwizzleNode->getOperand()->hasSideEffects());
TIntermTyped *swizzleOperandCopy = leftSwizzleNode->getOperand()->deepCopy();
TIntermSwizzle *leftSideNewSwizzle =
new TIntermSwizzle(swizzleOperandCopy, {leftSwizzleNode->getSwizzleOffsets()[i]});
// The resulting swizzle doesn't need folding here because it would've already been folded,
// this just turned it from a multielement into a single element swizzle.
// Most binary assignments are component-wise and so we just swizzle the right side to
// select one component for this particular single-element swizzle assignment. The only
// special case is multiplication by a matrix, which is not component-wise obviously.
TIntermTyped *newRightSide = CreateTempSymbolNode(rhsTempVariable);
TOperator newOp = parentBinNode->getOp();
if (newRightSide->getType().isMatrix() || newRightSide->getType().isVector())
{
if (newRightSide->getType().isMatrix())
{
ASSERT(parentBinNode->getOp() == EOpVectorTimesMatrixAssign);
// The `vec.xy *= mat` is being converted to something like `vec.x = (vec.xy *
// mat).x; vec.y = (vec.xy * mat).y;`
newOp = EOpAssign;
// TODO(anglebug.com/42267100): this matrix multiplication could be kept in a temp
// var.
newRightSide = new TIntermBinary(EOpVectorTimesMatrix, leftSwizzleNode->deepCopy(),
newRightSide);
}
ASSERT(newRightSide->getType().isVector());
// Now swizzle to select the i-th element.
newRightSide = new TIntermSwizzle(newRightSide, {i});
// No need to fold newRightSide because we are swizzling either a temporary or the
// binary matrix multiplication, so it won't be a swizzle.
}
else if (newRightSide->getType().isScalar())
{
// Needs no special handling, and can just be unmodified as the right hand side.
}
else
{
UNREACHABLE();
}
// At this point the right hand side should match the type of the left hand side.
ASSERT(newRightSide->isScalar());
ASSERT(newRightSide->getBasicType() == leftSideNewSwizzle->getBasicType());
singleElementSwizzleAssignments.emplace_back(
new TIntermBinary(newOp, leftSideNewSwizzle, newRightSide));
}
mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), parentBinNode,
std::move(singleElementSwizzleAssignments));
insertStatementsInParentBlock(insertionsBefore);
// Already traversed the left and right sides above.
return false;
}
} // anonymous namespace
bool RewriteMultielementSwizzleAssignment(TCompiler *compiler, TIntermBlock *root)
{
MultielementSwizzleAssignmentTraverser traverser(compiler);
root->traverse(&traverser);
return traverser.updateTree(compiler, root);
}
bool IsMultielementSwizzleAssignment(TOperator op, TIntermTyped *assignedNode)
{
if (!IsAssignment(op))
{
return false;
}
TIntermSwizzle *leftSwizzleNode = assignedNode->getAsSwizzleNode();
if (!leftSwizzleNode || leftSwizzleNode->getSwizzleOffsets().size() <= 1)
{
return false;
}
return true;
}
// Some multielement swizzles are too complicated without other AST transformations. Namely, swizzle
// assignments that are nested within other expressions or swizzle assignments whose operand has
// side effects. This AST transformation doesn't handle those swizzle assignments.
// `multielementSwizzleAssignment` must be an assignment to a multielement swizzle as determined by
// `IsMultielementSwizzleAssignment()`.
bool CanRewriteMultiElementSwizzleAssignmentEasily(TIntermBinary *multielementSwizzleAssignment,
TIntermNode *parent)
{
ASSERT(IsMultielementSwizzleAssignment(multielementSwizzleAssignment->getOp(),
multielementSwizzleAssignment->getLeft()));
return parent->getAsBlock() != nullptr && !multielementSwizzleAssignment->getLeft()
->getAsSwizzleNode()
->getOperand()
->hasSideEffects();
}
} // namespace sh