// Copyright (c) 2017 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. // // VectorizeVectorScalarArithmetic.cpp: Turn some arithmetic operations that operate on a float // vector-scalar pair into vector-vector operations. This is done recursively. Some scalar binary // operations inside vector constructors are also turned into vector operations. // // This is targeted to work around a bug in NVIDIA OpenGL drivers that was reproducible on NVIDIA // driver version 387.92. It works around the most common occurrences of the bug. #include "compiler/translator/VectorizeVectorScalarArithmetic.h" #include #include "compiler/translator/IntermNode.h" #include "compiler/translator/IntermTraverse.h" namespace sh { namespace { class VectorizeVectorScalarArithmeticTraverser : public TIntermTraverser { public: VectorizeVectorScalarArithmeticTraverser(TSymbolTable *symbolTable) : TIntermTraverser(true, false, false, symbolTable), mReplaced(false) { } bool didReplaceScalarsWithVectors() { return mReplaced; } void nextIteration() { mReplaced = false; mModifiedBlocks.clear(); } protected: bool visitBinary(Visit visit, TIntermBinary *node) override; bool visitAggregate(Visit visit, TIntermAggregate *node) override; private: // These helpers should only be called from visitAggregate when visiting a constructor. // argBinary is the only argument of the constructor. void replaceMathInsideConstructor(TIntermAggregate *node, TIntermBinary *argBinary); void replaceAssignInsideConstructor(const TIntermAggregate *node, const TIntermBinary *argBinary); static TIntermTyped *Vectorize(TIntermTyped *node, TType vectorType, TIntermTraverser::OriginalNode *originalNodeFate); bool mReplaced; std::set mModifiedBlocks; }; TIntermTyped *VectorizeVectorScalarArithmeticTraverser::Vectorize( TIntermTyped *node, TType vectorType, TIntermTraverser::OriginalNode *originalNodeFate) { ASSERT(node->isScalar()); vectorType.setQualifier(EvqTemporary); TIntermSequence vectorConstructorArgs; vectorConstructorArgs.push_back(node); TIntermAggregate *vectorized = TIntermAggregate::CreateConstructor(vectorType, &vectorConstructorArgs); TIntermTyped *vectorizedFolded = vectorized->fold(nullptr); if (originalNodeFate != nullptr) { if (vectorizedFolded != vectorized) { *originalNodeFate = OriginalNode::IS_DROPPED; } else { *originalNodeFate = OriginalNode::BECOMES_CHILD; } } return vectorizedFolded; } bool VectorizeVectorScalarArithmeticTraverser::visitBinary(Visit /*visit*/, TIntermBinary *node) { TIntermTyped *left = node->getLeft(); TIntermTyped *right = node->getRight(); ASSERT(left); ASSERT(right); switch (node->getOp()) { case EOpAdd: case EOpAddAssign: // Only these specific ops are necessary to turn into vector ops. break; default: return true; } if (node->getBasicType() != EbtFloat) { // Only float ops have reproduced the bug. return true; } if (left->isScalar() && right->isVector()) { ASSERT(!node->isAssignment()); ASSERT(!right->isArray()); OriginalNode originalNodeFate; TIntermTyped *leftVectorized = Vectorize(left, right->getType(), &originalNodeFate); queueReplacementWithParent(node, left, leftVectorized, originalNodeFate); mReplaced = true; // Don't replace more nodes in the same subtree on this traversal. However, nodes elsewhere // in the tree may still be replaced. return false; } else if (left->isVector() && right->isScalar()) { OriginalNode originalNodeFate; TIntermTyped *rightVectorized = Vectorize(right, left->getType(), &originalNodeFate); queueReplacementWithParent(node, right, rightVectorized, originalNodeFate); mReplaced = true; // Don't replace more nodes in the same subtree on this traversal. However, nodes elsewhere // in the tree may still be replaced. return false; } return true; } void VectorizeVectorScalarArithmeticTraverser::replaceMathInsideConstructor( TIntermAggregate *node, TIntermBinary *argBinary) { // Turn: // a * b // into: // gvec(a) * gvec(b) TIntermTyped *left = argBinary->getLeft(); TIntermTyped *right = argBinary->getRight(); ASSERT(left->isScalar() && right->isScalar()); TType leftVectorizedType = left->getType(); leftVectorizedType.setPrimarySize(static_cast(node->getType().getNominalSize())); TIntermTyped *leftVectorized = Vectorize(left, leftVectorizedType, nullptr); TType rightVectorizedType = right->getType(); rightVectorizedType.setPrimarySize( static_cast(node->getType().getNominalSize())); TIntermTyped *rightVectorized = Vectorize(right, rightVectorizedType, nullptr); TIntermBinary *newArg = new TIntermBinary(argBinary->getOp(), leftVectorized, rightVectorized); queueReplacementWithParent(node, argBinary, newArg, OriginalNode::IS_DROPPED); } void VectorizeVectorScalarArithmeticTraverser::replaceAssignInsideConstructor( const TIntermAggregate *node, const TIntermBinary *argBinary) { // Turn: // gvec(a *= b); // into: // // This is inserted into the parent block: // gvec s0 = gvec(a); // // // This goes where the gvec constructor used to be: // ((s0 *= b, a = s0.x), s0); TIntermTyped *left = argBinary->getLeft(); TIntermTyped *right = argBinary->getRight(); ASSERT(left->isScalar() && right->isScalar()); ASSERT(!left->hasSideEffects()); TType vecType = node->getType(); vecType.setQualifier(EvqTemporary); nextTemporaryId(); // gvec s0 = gvec(a); // s0 is called "tempAssignmentTarget" below. TIntermTyped *tempAssignmentTargetInitializer = Vectorize(left->deepCopy(), vecType, nullptr); TIntermDeclaration *tempAssignmentTargetDeclaration = createTempInitDeclaration(tempAssignmentTargetInitializer); // s0 *= b TOperator compoundAssignmentOp = argBinary->getOp(); if (compoundAssignmentOp == EOpMulAssign) { compoundAssignmentOp = EOpVectorTimesScalarAssign; } TIntermBinary *replacementCompoundAssignment = new TIntermBinary(compoundAssignmentOp, createTempSymbol(vecType), right->deepCopy()); // s0.x TVector swizzleXOffset; swizzleXOffset.push_back(0); TIntermSwizzle *tempAssignmentTargetX = new TIntermSwizzle(createTempSymbol(vecType), swizzleXOffset); // a = s0.x TIntermBinary *replacementAssignBackToTarget = new TIntermBinary(EOpAssign, left->deepCopy(), tempAssignmentTargetX); // s0 *= b, a = s0.x TIntermBinary *replacementSequenceLeft = new TIntermBinary(EOpComma, replacementCompoundAssignment, replacementAssignBackToTarget); // (s0 *= b, a = s0.x), s0 TIntermBinary *replacementSequence = new TIntermBinary(EOpComma, replacementSequenceLeft, createTempSymbol(vecType)); insertStatementInParentBlock(tempAssignmentTargetDeclaration); queueReplacement(replacementSequence, OriginalNode::IS_DROPPED); } bool VectorizeVectorScalarArithmeticTraverser::visitAggregate(Visit /*visit*/, TIntermAggregate *node) { // Transform scalar binary expressions inside vector constructors. if (!node->isConstructor() || !node->isVector() || node->getSequence()->size() != 1) { return true; } TIntermTyped *argument = node->getSequence()->back()->getAsTyped(); ASSERT(argument); if (!argument->isScalar() || argument->getBasicType() != EbtFloat) { return true; } TIntermBinary *argBinary = argument->getAsBinaryNode(); if (!argBinary) { return true; } // Only specific ops are necessary to change. switch (argBinary->getOp()) { case EOpMul: case EOpDiv: { replaceMathInsideConstructor(node, argBinary); mReplaced = true; // Don't replace more nodes in the same subtree on this traversal. However, nodes // elsewhere in the tree may still be replaced. return false; } case EOpMulAssign: case EOpDivAssign: { // The case where the left side has side effects is too complicated to deal with, so we // leave that be. if (!argBinary->getLeft()->hasSideEffects()) { const TIntermBlock *parentBlock = getParentBlock(); // We can't do more than one insertion to the same block on the same traversal. if (mModifiedBlocks.find(parentBlock) == mModifiedBlocks.end()) { replaceAssignInsideConstructor(node, argBinary); mModifiedBlocks.insert(parentBlock); mReplaced = true; // Don't replace more nodes in the same subtree on this traversal. // However, nodes elsewhere in the tree may still be replaced. return false; } } break; } default: return true; } return true; } } // anonymous namespace void VectorizeVectorScalarArithmetic(TIntermBlock *root, TSymbolTable *symbolTable) { VectorizeVectorScalarArithmeticTraverser traverser(symbolTable); do { traverser.nextIteration(); root->traverse(&traverser); traverser.updateTree(); } while (traverser.didReplaceScalarsWithVectors()); } } // namespace sh