// // Copyright 2022 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. // // EmulateAdvancedBlendEquations.cpp: Emulate advanced blend equations by implicitly reading back // from the color attachment (as an input attachment) and apply the equation function based on a // uniform. // #include "compiler/translator/tree_ops/spirv/EmulateAdvancedBlendEquations.h" #include #include "GLSLANG/ShaderVars.h" #include "common/PackedEnums.h" #include "compiler/translator/Compiler.h" #include "compiler/translator/StaticType.h" #include "compiler/translator/SymbolTable.h" #include "compiler/translator/tree_util/DriverUniform.h" #include "compiler/translator/tree_util/FindMain.h" #include "compiler/translator/tree_util/IntermNode_util.h" #include "compiler/translator/tree_util/IntermTraverse.h" #include "compiler/translator/tree_util/RunAtTheEndOfShader.h" namespace sh { namespace { // All helper functions that may be generated. class Builder { public: Builder(TCompiler *compiler, TSymbolTable *symbolTable, const DriverUniform *driverUniforms, std::vector *uniforms, const AdvancedBlendEquations &advancedBlendEquations) : mCompiler(compiler), mSymbolTable(symbolTable), mDriverUniforms(driverUniforms), mUniforms(uniforms), mAdvancedBlendEquations(advancedBlendEquations) {} bool build(TIntermBlock *root); private: void findColorOutput(TIntermBlock *root); void createSubpassInputVar(TIntermBlock *root); void generateHslHelperFunctions(); void generateBlendFunctions(); void insertGeneratedFunctions(TIntermBlock *root); TIntermTyped *divideFloatNode(TIntermTyped *dividend, TIntermTyped *divisor); TIntermSymbol *premultiplyAlpha(TIntermBlock *blendBlock, TIntermTyped *var, const char *name); void generatePreamble(TIntermBlock *blendBlock); void generateEquationSwitch(TIntermBlock *blendBlock); TCompiler *mCompiler; TSymbolTable *mSymbolTable; const DriverUniform *mDriverUniforms; std::vector *mUniforms; const AdvancedBlendEquations &mAdvancedBlendEquations; // The color input and output. Output is the blend source, and input is the destination. const TVariable *mSubpassInputVar = nullptr; const TVariable *mOutputVar = nullptr; // The value of output, premultiplied by alpha TIntermSymbol *mSrc = nullptr; // The value of input, premultiplied by alpha TIntermSymbol *mDst = nullptr; // p0, p1 and p2 coefficients TIntermSymbol *mP0 = nullptr; TIntermSymbol *mP1 = nullptr; TIntermSymbol *mP2 = nullptr; // Functions implementing an advanced blend equation: angle::PackedEnumMap mBlendFuncs = {}; // HSL helpers: TIntermFunctionDefinition *mMinv3 = nullptr; TIntermFunctionDefinition *mMaxv3 = nullptr; TIntermFunctionDefinition *mLumv3 = nullptr; TIntermFunctionDefinition *mSatv3 = nullptr; TIntermFunctionDefinition *mClipColor = nullptr; TIntermFunctionDefinition *mSetLum = nullptr; TIntermFunctionDefinition *mSetLumSat = nullptr; }; bool Builder::build(TIntermBlock *root) { // Find the output variable for which advanced blend is specified. Note that advanced blend can // only used when rendering is done to a single color attachment. findColorOutput(root); if (mSubpassInputVar == nullptr) { createSubpassInputVar(root); } // If any HSL blend equation is used, generate a few utility functions used in Table X.2 in the // spec. if (mAdvancedBlendEquations.anyHsl()) { generateHslHelperFunctions(); } // Generate a function for each enabled blend equation. This is |f| in the spec. generateBlendFunctions(); // Insert the generated functions to root. insertGeneratedFunctions(root); // Prepare for blend by: // // - Premultiplying src and dst color by alpha // - Calculating p0, p1 and p2 // // Note that the color coefficients (X,Y,Z) are always (1,1,1) in the KHR extension (they were // not in the NV extension), so they are implicitly dropped. TIntermBlock *blendBlock = new TIntermBlock; generatePreamble(blendBlock); // Generate the |switch| that calls the right function based on a driver uniform. generateEquationSwitch(blendBlock); // Place the entire blend block under an if (equation != 0) TIntermTyped *equationUniform = mDriverUniforms->getAdvancedBlendEquation(); TIntermTyped *notZero = new TIntermBinary(EOpNotEqual, equationUniform, CreateUIntNode(0)); TIntermIfElse *blend = new TIntermIfElse(notZero, blendBlock, nullptr); return RunAtTheEndOfShader(mCompiler, root, blend, mSymbolTable); } void Builder::findColorOutput(TIntermBlock *root) { for (TIntermNode *node : *root->getSequence()) { TIntermDeclaration *asDecl = node->getAsDeclarationNode(); if (asDecl == nullptr) { continue; } // SeparateDeclarations should have already been run. ASSERT(asDecl->getSequence()->size() == 1u); TIntermSymbol *symbol = asDecl->getSequence()->front()->getAsSymbolNode(); if (symbol == nullptr) { continue; } const TType &type = symbol->getType(); if (type.getQualifier() == EvqFragmentOut || type.getQualifier() == EvqFragmentInOut) { // There can only be one output with advanced blend per spec. // If there are multiple outputs, take the one one with location 0. if (mOutputVar == nullptr || mOutputVar->getType().getLayoutQualifier().location > 0) { mOutputVar = &symbol->variable(); } } if (IsSubpassInputType(type.getBasicType())) { // There can only be one output with advanced blend, so there can only be a maximum of // one subpass input already defined (by framebuffer fetch emulation). ASSERT(mSubpassInputVar == nullptr); mSubpassInputVar = &symbol->variable(); } } // This transformation is only ever called when advanced blend is specified. ASSERT(mOutputVar != nullptr); } TIntermSymbol *MakeVariable(TSymbolTable *symbolTable, const char *name, const TType *type) { const TVariable *var = new TVariable(symbolTable, ImmutableString(name), type, SymbolType::AngleInternal); return new TIntermSymbol(var); } void Builder::createSubpassInputVar(TIntermBlock *root) { const TPrecision precision = mOutputVar->getType().getPrecision(); // The input attachment index used for this color attachment would be identical to its location // (or implicitly 0 if not specified). const unsigned int inputAttachmentIndex = std::max(0, mOutputVar->getType().getLayoutQualifier().location); // Note that blending can only happen on float/fixed-point output. ASSERT(mOutputVar->getType().getBasicType() == EbtFloat); // Create the subpass input uniform. TType *inputAttachmentType = new TType(EbtSubpassInput, precision, EvqUniform, 1); TLayoutQualifier inputAttachmentQualifier = inputAttachmentType->getLayoutQualifier(); inputAttachmentQualifier.inputAttachmentIndex = inputAttachmentIndex; inputAttachmentType->setLayoutQualifier(inputAttachmentQualifier); const char *kSubpassInputName = "ANGLEFragmentInput"; TIntermSymbol *subpassInputSymbol = MakeVariable(mSymbolTable, kSubpassInputName, inputAttachmentType); mSubpassInputVar = &subpassInputSymbol->variable(); // Add its declaration to the shader. TIntermDeclaration *subpassInputDecl = new TIntermDeclaration; subpassInputDecl->appendDeclarator(subpassInputSymbol); root->insertStatement(0, subpassInputDecl); // Add the new subpass input to the list of uniforms. ShaderVariable subpassInputUniform; subpassInputUniform.active = true; subpassInputUniform.staticUse = true; subpassInputUniform.name.assign(kSubpassInputName); subpassInputUniform.mappedName.assign(kSubpassInputName); subpassInputUniform.isFragmentInOut = true; subpassInputUniform.location = inputAttachmentIndex; mUniforms->push_back(subpassInputUniform); } TIntermTyped *Float(float f) { return CreateFloatNode(f, EbpMedium); } TFunction *MakeFunction(TSymbolTable *symbolTable, const char *name, const TType *returnType, const TVector &args) { TFunction *function = new TFunction(symbolTable, ImmutableString(name), SymbolType::AngleInternal, returnType, false); for (const TVariable *arg : args) { function->addParameter(arg); } return function; } TIntermFunctionDefinition *MakeFunctionDefinition(const TFunction *function, TIntermBlock *body) { return new TIntermFunctionDefinition(new TIntermFunctionPrototype(function), body); } TIntermFunctionDefinition *MakeSimpleFunctionDefinition(TSymbolTable *symbolTable, const char *name, TIntermTyped *returnExpression, const TVector &args) { TVector argsAsVar; for (TIntermSymbol *arg : args) { argsAsVar.push_back(&arg->variable()); } TIntermBlock *body = new TIntermBlock; body->appendStatement(new TIntermBranch(EOpReturn, returnExpression)); const TFunction *function = MakeFunction(symbolTable, name, &returnExpression->getType(), argsAsVar); return MakeFunctionDefinition(function, body); } void Builder::generateHslHelperFunctions() { const TPrecision precision = mOutputVar->getType().getPrecision(); TType *floatType = new TType(EbtFloat, precision, EvqTemporary, 1); TType *vec3Type = new TType(EbtFloat, precision, EvqTemporary, 3); TType *vec3ParamType = new TType(EbtFloat, precision, EvqParamIn, 3); // float ANGLE_minv3(vec3 c) // { // return min(min(c.r, c.g), c.b); // } { TIntermSymbol *c = MakeVariable(mSymbolTable, "c", vec3ParamType); TIntermTyped *cR = new TIntermSwizzle(c, {0}); TIntermTyped *cG = new TIntermSwizzle(c->deepCopy(), {1}); TIntermTyped *cB = new TIntermSwizzle(c->deepCopy(), {2}); // min(c.r, c.g) TIntermSequence cRcG = {cR, cG}; TIntermTyped *minRG = CreateBuiltInFunctionCallNode("min", &cRcG, *mSymbolTable, 100); // min(min(c.r, c.g), c.b) TIntermSequence minRGcB = {minRG, cB}; TIntermTyped *minRGB = CreateBuiltInFunctionCallNode("min", &minRGcB, *mSymbolTable, 100); mMinv3 = MakeSimpleFunctionDefinition(mSymbolTable, "ANGLE_minv3", minRGB, {c}); } // float ANGLE_maxv3(vec3 c) // { // return max(max(c.r, c.g), c.b); // } { TIntermSymbol *c = MakeVariable(mSymbolTable, "c", vec3ParamType); TIntermTyped *cR = new TIntermSwizzle(c, {0}); TIntermTyped *cG = new TIntermSwizzle(c->deepCopy(), {1}); TIntermTyped *cB = new TIntermSwizzle(c->deepCopy(), {2}); // max(c.r, c.g) TIntermSequence cRcG = {cR, cG}; TIntermTyped *maxRG = CreateBuiltInFunctionCallNode("max", &cRcG, *mSymbolTable, 100); // max(max(c.r, c.g), c.b) TIntermSequence maxRGcB = {maxRG, cB}; TIntermTyped *maxRGB = CreateBuiltInFunctionCallNode("max", &maxRGcB, *mSymbolTable, 100); mMaxv3 = MakeSimpleFunctionDefinition(mSymbolTable, "ANGLE_maxv3", maxRGB, {c}); } // float ANGLE_lumv3(vec3 c) // { // return dot(c, vec3(0.30f, 0.59f, 0.11f)); // } { TIntermSymbol *c = MakeVariable(mSymbolTable, "c", vec3ParamType); constexpr std::array kCoeff = {0.30f, 0.59f, 0.11f}; TIntermConstantUnion *coeff = CreateVecNode(kCoeff.data(), 3, EbpMedium); // dot(c, coeff) TIntermSequence cCoeff = {c, coeff}; TIntermTyped *dot = CreateBuiltInFunctionCallNode("dot", &cCoeff, *mSymbolTable, 100); mLumv3 = MakeSimpleFunctionDefinition(mSymbolTable, "ANGLE_lumv3", dot, {c}); } // float ANGLE_satv3(vec3 c) // { // return ANGLE_maxv3(c) - ANGLE_minv3(c); // } { TIntermSymbol *c = MakeVariable(mSymbolTable, "c", vec3ParamType); // ANGLE_maxv3(c) TIntermSequence cMaxArg = {c}; TIntermTyped *maxv3 = TIntermAggregate::CreateFunctionCall(*mMaxv3->getFunction(), &cMaxArg); // ANGLE_minv3(c) TIntermSequence cMinArg = {c->deepCopy()}; TIntermTyped *minv3 = TIntermAggregate::CreateFunctionCall(*mMinv3->getFunction(), &cMinArg); // max - min TIntermTyped *diff = new TIntermBinary(EOpSub, maxv3, minv3); mSatv3 = MakeSimpleFunctionDefinition(mSymbolTable, "ANGLE_satv3", diff, {c}); } // vec3 ANGLE_clip_color(vec3 color) // { // float lum = ANGLE_lumv3(color); // float mincol = ANGLE_minv3(color); // float maxcol = ANGLE_maxv3(color); // if (mincol < 0.0f) // { // color = lum + ((color - lum) * lum) / (lum - mincol); // } // if (maxcol > 1.0f) // { // color = lum + ((color - lum) * (1.0f - lum)) / (maxcol - lum); // } // return color; // } { TIntermSymbol *color = MakeVariable(mSymbolTable, "color", vec3ParamType); TIntermSymbol *lum = MakeVariable(mSymbolTable, "lum", floatType); TIntermSymbol *mincol = MakeVariable(mSymbolTable, "mincol", floatType); TIntermSymbol *maxcol = MakeVariable(mSymbolTable, "maxcol", floatType); // ANGLE_lumv3(color) TIntermSequence cLumArg = {color}; TIntermTyped *lumv3 = TIntermAggregate::CreateFunctionCall(*mLumv3->getFunction(), &cLumArg); // ANGLE_minv3(color) TIntermSequence cMinArg = {color->deepCopy()}; TIntermTyped *minv3 = TIntermAggregate::CreateFunctionCall(*mMinv3->getFunction(), &cMinArg); // ANGLE_maxv3(color) TIntermSequence cMaxArg = {color->deepCopy()}; TIntermTyped *maxv3 = TIntermAggregate::CreateFunctionCall(*mMaxv3->getFunction(), &cMaxArg); TIntermBlock *body = new TIntermBlock; body->appendStatement(CreateTempInitDeclarationNode(&lum->variable(), lumv3)); body->appendStatement(CreateTempInitDeclarationNode(&mincol->variable(), minv3)); body->appendStatement(CreateTempInitDeclarationNode(&maxcol->variable(), maxv3)); // color - lum TIntermTyped *colorMinusLum = new TIntermBinary(EOpSub, color->deepCopy(), lum); // (color - lum) * lum TIntermTyped *colorMinusLumTimesLum = new TIntermBinary(EOpVectorTimesScalar, colorMinusLum, lum->deepCopy()); // lum - mincol TIntermTyped *lumMinusMincol = new TIntermBinary(EOpSub, lum->deepCopy(), mincol); // ((color - lum) * lum) / (lum - mincol) TIntermTyped *negativeMincolLumOffset = new TIntermBinary(EOpDiv, colorMinusLumTimesLum, lumMinusMincol); // lum + ((color - lum) * lum) / (lum - mincol) TIntermTyped *negativeMincolOffset = new TIntermBinary(EOpAdd, lum->deepCopy(), negativeMincolLumOffset); // color = lum + ((color - lum) * lum) / (lum - mincol) TIntermBlock *if1Body = new TIntermBlock; if1Body->appendStatement( new TIntermBinary(EOpAssign, color->deepCopy(), negativeMincolOffset)); // mincol < 0.0f TIntermTyped *lessZero = new TIntermBinary(EOpLessThan, mincol->deepCopy(), Float(0)); // if (mincol < 0.0f) ... body->appendStatement(new TIntermIfElse(lessZero, if1Body, nullptr)); // 1.0f - lum TIntermTyped *oneMinusLum = new TIntermBinary(EOpSub, Float(1.0f), lum->deepCopy()); // (color - lum) * (1.0f - lum) TIntermTyped *colorMinusLumTimesOneMinusLum = new TIntermBinary(EOpVectorTimesScalar, colorMinusLum->deepCopy(), oneMinusLum); // maxcol - lum TIntermTyped *maxcolMinusLum = new TIntermBinary(EOpSub, maxcol, lum->deepCopy()); // (color - lum) * (1.0f - lum) / (maxcol - lum) TIntermTyped *largeMaxcolLumOffset = new TIntermBinary(EOpDiv, colorMinusLumTimesOneMinusLum, maxcolMinusLum); // lum + (color - lum) * (1.0f - lum) / (maxcol - lum) TIntermTyped *largeMaxcolOffset = new TIntermBinary(EOpAdd, lum->deepCopy(), largeMaxcolLumOffset); // color = lum + (color - lum) * (1.0f - lum) / (maxcol - lum) TIntermBlock *if2Body = new TIntermBlock; if2Body->appendStatement( new TIntermBinary(EOpAssign, color->deepCopy(), largeMaxcolOffset)); // maxcol > 1.0f TIntermTyped *largerOne = new TIntermBinary(EOpGreaterThan, maxcol->deepCopy(), Float(1)); // if (maxcol > 1.0f) ... body->appendStatement(new TIntermIfElse(largerOne, if2Body, nullptr)); body->appendStatement(new TIntermBranch(EOpReturn, color->deepCopy())); const TFunction *function = MakeFunction(mSymbolTable, "ANGLE_clip_color", vec3Type, {&color->variable()}); mClipColor = MakeFunctionDefinition(function, body); } // vec3 ANGLE_set_lum(vec3 cbase, vec3 clum) // { // float lbase = ANGLE_lumv3(cbase); // float llum = ANGLE_lumv3(clum); // float ldiff = llum - lbase; // vec3 color = cbase + ldiff; // return ANGLE_clip_color(color); // } { TIntermSymbol *cbase = MakeVariable(mSymbolTable, "cbase", vec3ParamType); TIntermSymbol *clum = MakeVariable(mSymbolTable, "clum", vec3ParamType); // ANGLE_lumv3(cbase) TIntermSequence cbaseArg = {cbase}; TIntermTyped *lbase = TIntermAggregate::CreateFunctionCall(*mLumv3->getFunction(), &cbaseArg); // ANGLE_lumv3(clum) TIntermSequence clumArg = {clum}; TIntermTyped *llum = TIntermAggregate::CreateFunctionCall(*mLumv3->getFunction(), &clumArg); // llum - lbase TIntermTyped *ldiff = new TIntermBinary(EOpSub, llum, lbase); // cbase + ldiff TIntermTyped *color = new TIntermBinary(EOpAdd, cbase->deepCopy(), ldiff); // ANGLE_clip_color(color); TIntermSequence clipColorArg = {color}; TIntermTyped *result = TIntermAggregate::CreateFunctionCall(*mClipColor->getFunction(), &clipColorArg); TIntermBlock *body = new TIntermBlock; body->appendStatement(new TIntermBranch(EOpReturn, result)); const TFunction *function = MakeFunction(mSymbolTable, "ANGLE_set_lum", vec3Type, {&cbase->variable(), &clum->variable()}); mSetLum = MakeFunctionDefinition(function, body); } // vec3 ANGLE_set_lum_sat(vec3 cbase, vec3 csat, vec3 clum) // { // float minbase = ANGLE_minv3(cbase); // float sbase = ANGLE_satv3(cbase); // float ssat = ANGLE_satv3(csat); // vec3 color; // if (sbase > 0.0f) // { // color = (cbase - minbase) * ssat / sbase; // } // else // { // color = vec3(0.0f); // } // return ANGLE_set_lum(color, clum); // } { TIntermSymbol *cbase = MakeVariable(mSymbolTable, "cbase", vec3ParamType); TIntermSymbol *csat = MakeVariable(mSymbolTable, "csat", vec3ParamType); TIntermSymbol *clum = MakeVariable(mSymbolTable, "clum", vec3ParamType); TIntermSymbol *minbase = MakeVariable(mSymbolTable, "minbase", floatType); TIntermSymbol *sbase = MakeVariable(mSymbolTable, "sbase", floatType); TIntermSymbol *ssat = MakeVariable(mSymbolTable, "ssat", floatType); // ANGLE_minv3(cbase) TIntermSequence cMinArg = {cbase}; TIntermTyped *minv3 = TIntermAggregate::CreateFunctionCall(*mMinv3->getFunction(), &cMinArg); // ANGLE_satv3(cbase) TIntermSequence cSatArg = {cbase->deepCopy()}; TIntermTyped *baseSatv3 = TIntermAggregate::CreateFunctionCall(*mSatv3->getFunction(), &cSatArg); // ANGLE_satv3(csat) TIntermSequence sSatArg = {csat}; TIntermTyped *satSatv3 = TIntermAggregate::CreateFunctionCall(*mSatv3->getFunction(), &sSatArg); TIntermBlock *body = new TIntermBlock; body->appendStatement(CreateTempInitDeclarationNode(&minbase->variable(), minv3)); body->appendStatement(CreateTempInitDeclarationNode(&sbase->variable(), baseSatv3)); body->appendStatement(CreateTempInitDeclarationNode(&ssat->variable(), satSatv3)); // cbase - minbase TIntermTyped *cbaseMinusMinbase = new TIntermBinary(EOpSub, cbase->deepCopy(), minbase); // (cbase - minbase) * ssat TIntermTyped *cbaseMinusMinbaseTimesSsat = new TIntermBinary(EOpVectorTimesScalar, cbaseMinusMinbase, ssat); // (cbase - minbase) * ssat / sbase TIntermTyped *colorSbaseGreaterZero = new TIntermBinary(EOpDiv, cbaseMinusMinbaseTimesSsat, sbase); // sbase > 0.0f TIntermTyped *greaterZero = new TIntermBinary(EOpGreaterThan, sbase->deepCopy(), Float(0)); // sbase > 0.0f ? (cbase - minbase) * ssat / sbase : vec3(0.0) TIntermTyped *color = new TIntermTernary(greaterZero, colorSbaseGreaterZero, CreateZeroNode(*vec3Type)); // ANGLE_set_lum(color); TIntermSequence setLumArg = {color, clum}; TIntermTyped *result = TIntermAggregate::CreateFunctionCall(*mSetLum->getFunction(), &setLumArg); body->appendStatement(new TIntermBranch(EOpReturn, result)); const TFunction *function = MakeFunction(mSymbolTable, "ANGLE_set_lum_sat", vec3Type, {&cbase->variable(), &csat->variable(), &clum->variable()}); mSetLumSat = MakeFunctionDefinition(function, body); } } void Builder::generateBlendFunctions() { const TPrecision precision = mOutputVar->getType().getPrecision(); TType *floatParamType = new TType(EbtFloat, precision, EvqParamIn, 1); TType *vec3ParamType = new TType(EbtFloat, precision, EvqParamIn, 3); gl::BlendEquationBitSet enabledBlendEquations(mAdvancedBlendEquations.bits()); for (gl::BlendEquationType equation : enabledBlendEquations) { switch (equation) { case gl::BlendEquationType::Multiply: // float ANGLE_blend_multiply(float src, float dst) // { // return src * dst; // } { TIntermSymbol *src = MakeVariable(mSymbolTable, "src", floatParamType); TIntermSymbol *dst = MakeVariable(mSymbolTable, "dst", floatParamType); // src * dst TIntermTyped *result = new TIntermBinary(EOpMul, src, dst); mBlendFuncs[equation] = MakeSimpleFunctionDefinition( mSymbolTable, "ANGLE_blend_multiply", result, {src, dst}); } break; case gl::BlendEquationType::Screen: // float ANGLE_blend_screen(float src, float dst) // { // return src + dst - src * dst; // } { TIntermSymbol *src = MakeVariable(mSymbolTable, "src", floatParamType); TIntermSymbol *dst = MakeVariable(mSymbolTable, "dst", floatParamType); // src + dst TIntermTyped *sum = new TIntermBinary(EOpAdd, src, dst); // src * dst TIntermTyped *mul = new TIntermBinary(EOpMul, src->deepCopy(), dst->deepCopy()); // src + dst - src * dst TIntermTyped *result = new TIntermBinary(EOpSub, sum, mul); mBlendFuncs[equation] = MakeSimpleFunctionDefinition( mSymbolTable, "ANGLE_blend_screen", result, {src, dst}); } break; case gl::BlendEquationType::Overlay: case gl::BlendEquationType::Hardlight: // float ANGLE_blend_overlay(float src, float dst) // { // if (dst <= 0.5f) // { // return (2.0f * src * dst); // } // else // { // return (1.0f - 2.0f * (1.0f - src) * (1.0f - dst)); // } // // // Equivalently generated as: // // return dst <= 0.5f ? 2.*src*dst : 2.*(src+dst) - 2.*src*dst - 1.; // } // // float ANGLE_blend_hardlight(float src, float dst) // { // // Same as overlay, with the |if| checking |src| instead of |dst|. // } { TIntermSymbol *src = MakeVariable(mSymbolTable, "src", floatParamType); TIntermSymbol *dst = MakeVariable(mSymbolTable, "dst", floatParamType); // src + dst TIntermTyped *sum = new TIntermBinary(EOpAdd, src, dst); // 2 * (src + dst) TIntermTyped *sum2 = new TIntermBinary(EOpMul, sum, Float(2)); // src * dst TIntermTyped *mul = new TIntermBinary(EOpMul, src->deepCopy(), dst->deepCopy()); // 2 * src * dst TIntermTyped *mul2 = new TIntermBinary(EOpMul, mul, Float(2)); // 2 * (src + dst) - 2 * src * dst TIntermTyped *sum2MinusMul2 = new TIntermBinary(EOpSub, sum2, mul2); // 2 * (src + dst) - 2 * src * dst - 1 TIntermTyped *sum2MinusMul2Minus1 = new TIntermBinary(EOpSub, sum2MinusMul2, Float(1)); // dst[src] <= 0.5 TIntermSymbol *conditionSymbol = equation == gl::BlendEquationType::Overlay ? dst : src; TIntermTyped *lessHalf = new TIntermBinary( EOpLessThanEqual, conditionSymbol->deepCopy(), Float(0.5)); // dst[src] <= 0.5f ? ... TIntermTyped *result = new TIntermTernary(lessHalf, mul2->deepCopy(), sum2MinusMul2Minus1); mBlendFuncs[equation] = MakeSimpleFunctionDefinition( mSymbolTable, equation == gl::BlendEquationType::Overlay ? "ANGLE_blend_overlay" : "ANGLE_blend_hardlight", result, {src, dst}); } break; case gl::BlendEquationType::Darken: // float ANGLE_blend_darken(float src, float dst) // { // return min(src, dst); // } { TIntermSymbol *src = MakeVariable(mSymbolTable, "src", floatParamType); TIntermSymbol *dst = MakeVariable(mSymbolTable, "dst", floatParamType); // src * dst TIntermSequence minArgs = {src, dst}; TIntermTyped *result = CreateBuiltInFunctionCallNode("min", &minArgs, *mSymbolTable, 100); mBlendFuncs[equation] = MakeSimpleFunctionDefinition( mSymbolTable, "ANGLE_blend_darken", result, {src, dst}); } break; case gl::BlendEquationType::Lighten: // float ANGLE_blend_lighten(float src, float dst) // { // return max(src, dst); // } { TIntermSymbol *src = MakeVariable(mSymbolTable, "src", floatParamType); TIntermSymbol *dst = MakeVariable(mSymbolTable, "dst", floatParamType); // src * dst TIntermSequence maxArgs = {src, dst}; TIntermTyped *result = CreateBuiltInFunctionCallNode("max", &maxArgs, *mSymbolTable, 100); mBlendFuncs[equation] = MakeSimpleFunctionDefinition( mSymbolTable, "ANGLE_blend_lighten", result, {src, dst}); } break; case gl::BlendEquationType::Colordodge: // float ANGLE_blend_dodge(float src, float dst) // { // if (dst <= 0.0f) // { // return 0.0; // } // else if (src >= 1.0f) // dst > 0.0 // { // return 1.0; // } // else // dst > 0.0 && src < 1.0 // { // return min(1.0, dst / (1.0 - src)); // } // // // Equivalently generated as: // // return dst <= 0. ? 0. : src >= 1. ? 1. : min(1., dst / (1. - src)); // } { TIntermSymbol *src = MakeVariable(mSymbolTable, "src", floatParamType); TIntermSymbol *dst = MakeVariable(mSymbolTable, "dst", floatParamType); // 1. - src TIntermTyped *oneMinusSrc = new TIntermBinary(EOpSub, Float(1), src); // dst / (1. - src) TIntermTyped *dstDivOneMinusSrc = new TIntermBinary(EOpDiv, dst, oneMinusSrc); // min(1., dst / (1. - src)) TIntermSequence minArgs = {Float(1), dstDivOneMinusSrc}; TIntermTyped *result = CreateBuiltInFunctionCallNode("min", &minArgs, *mSymbolTable, 100); // src >= 1 TIntermTyped *greaterOne = new TIntermBinary(EOpGreaterThanEqual, src->deepCopy(), Float(1)); // src >= 1. ? ... result = new TIntermTernary(greaterOne, Float(1), result); // dst <= 0 TIntermTyped *lessZero = new TIntermBinary(EOpLessThanEqual, dst->deepCopy(), Float(0)); // dst <= 0. ? ... result = new TIntermTernary(lessZero, Float(0), result); mBlendFuncs[equation] = MakeSimpleFunctionDefinition( mSymbolTable, "ANGLE_blend_dodge", result, {src, dst}); } break; case gl::BlendEquationType::Colorburn: // float ANGLE_blend_burn(float src, float dst) // { // if (dst >= 1.0f) // { // return 1.0; // } // else if (src <= 0.0f) // dst < 1.0 // { // return 0.0; // } // else // dst < 1.0 && src > 0.0 // { // return 1.0f - min(1.0f, (1.0f - dst) / src); // } // // // Equivalently generated as: // // return dst >= 1. ? 1. : src <= 0. ? 0. : 1. - min(1., (1. - dst) / src); // } { TIntermSymbol *src = MakeVariable(mSymbolTable, "src", floatParamType); TIntermSymbol *dst = MakeVariable(mSymbolTable, "dst", floatParamType); // 1. - dst TIntermTyped *oneMinusDst = new TIntermBinary(EOpSub, Float(1), dst); // (1. - dst) / src TIntermTyped *oneMinusDstDivSrc = new TIntermBinary(EOpDiv, oneMinusDst, src); // min(1., (1. - dst) / src) TIntermSequence minArgs = {Float(1), oneMinusDstDivSrc}; TIntermTyped *result = CreateBuiltInFunctionCallNode("min", &minArgs, *mSymbolTable, 100); // 1. - min(1., (1. - dst) / src) result = new TIntermBinary(EOpSub, Float(1), result); // src <= 0 TIntermTyped *lessZero = new TIntermBinary(EOpLessThanEqual, src->deepCopy(), Float(0)); // src <= 0. ? ... result = new TIntermTernary(lessZero, Float(0), result); // dst >= 1 TIntermTyped *greaterOne = new TIntermBinary(EOpGreaterThanEqual, dst->deepCopy(), Float(1)); // dst >= 1. ? ... result = new TIntermTernary(greaterOne, Float(1), result); mBlendFuncs[equation] = MakeSimpleFunctionDefinition( mSymbolTable, "ANGLE_blend_burn", result, {src, dst}); } break; case gl::BlendEquationType::Softlight: // float ANGLE_blend_softlight(float src, float dst) // { // if (src <= 0.5f) // { // return (dst - (1.0f - 2.0f * src) * dst * (1.0f - dst)); // } // else if (dst <= 0.25f) // src > 0.5 // { // return (dst + (2.0f * src - 1.0f) * dst * ((16.0f * dst - 12.0f) * dst // + 3.0f)); // } // else // src > 0.5 && dst > 0.25 // { // return (dst + (2.0f * src - 1.0f) * (sqrt(dst) - dst)); // } // // // Equivalently generated as: // // return dst + (2. * src - 1.) * ( // // src <= 0.5 ? dst * (1. - dst) : // // dst <= 0.25 ? dst * ((16. * dst - 12.) * dst + 3.) : // // sqrt(dst) - dst) // } { TIntermSymbol *src = MakeVariable(mSymbolTable, "src", floatParamType); TIntermSymbol *dst = MakeVariable(mSymbolTable, "dst", floatParamType); // 2. * src TIntermTyped *src2 = new TIntermBinary(EOpMul, Float(2), src); // 2. * src - 1. TIntermTyped *src2Minus1 = new TIntermBinary(EOpSub, src2, Float(1)); // 1. - dst TIntermTyped *oneMinusDst = new TIntermBinary(EOpSub, Float(1), dst); // dst * (1. - dst) TIntermTyped *dstTimesOneMinusDst = new TIntermBinary(EOpMul, dst->deepCopy(), oneMinusDst); // 16. * dst TIntermTyped *dst16 = new TIntermBinary(EOpMul, Float(16), dst->deepCopy()); // 16. * dst - 12. TIntermTyped *dst16Minus12 = new TIntermBinary(EOpSub, dst16, Float(12)); // (16. * dst - 12.) * dst TIntermTyped *dst16Minus12TimesDst = new TIntermBinary(EOpMul, dst16Minus12, dst->deepCopy()); // (16. * dst - 12.) * dst + 3. TIntermTyped *dst16Minus12TimesDstPlus3 = new TIntermBinary(EOpAdd, dst16Minus12TimesDst, Float(3)); // dst * ((16. * dst - 12.) * dst + 3.) TIntermTyped *dstTimesDst16Minus12TimesDstPlus3 = new TIntermBinary(EOpMul, dst->deepCopy(), dst16Minus12TimesDstPlus3); // sqrt(dst) TIntermSequence sqrtArg = {dst->deepCopy()}; TIntermTyped *sqrtDst = CreateBuiltInFunctionCallNode("sqrt", &sqrtArg, *mSymbolTable, 100); // sqrt(dst) - dst TIntermTyped *sqrtDstMinusDst = new TIntermBinary(EOpSub, sqrtDst, dst->deepCopy()); // dst <= 0.25 TIntermTyped *lessQuarter = new TIntermBinary(EOpLessThanEqual, dst->deepCopy(), Float(0.25)); // dst <= 0.25 ? ... TIntermTyped *result = new TIntermTernary( lessQuarter, dstTimesDst16Minus12TimesDstPlus3, sqrtDstMinusDst); // src <= 0.5 TIntermTyped *lessHalf = new TIntermBinary(EOpLessThanEqual, src->deepCopy(), Float(0.5)); // src <= 0.5 ? ... result = new TIntermTernary(lessHalf, dstTimesOneMinusDst, result); // (2. * src - 1.) * ... result = new TIntermBinary(EOpMul, src2Minus1, result); // dst + (2. * src - 1.) * ... result = new TIntermBinary(EOpAdd, dst->deepCopy(), result); mBlendFuncs[equation] = MakeSimpleFunctionDefinition( mSymbolTable, "ANGLE_blend_softlight", result, {src, dst}); } break; case gl::BlendEquationType::Difference: // float ANGLE_blend_difference(float src, float dst) // { // return abs(dst - src); // } { TIntermSymbol *src = MakeVariable(mSymbolTable, "src", floatParamType); TIntermSymbol *dst = MakeVariable(mSymbolTable, "dst", floatParamType); // dst - src TIntermTyped *dstMinusSrc = new TIntermBinary(EOpSub, dst, src); // abs(dst - src) TIntermSequence absArgs = {dstMinusSrc}; TIntermTyped *result = CreateBuiltInFunctionCallNode("abs", &absArgs, *mSymbolTable, 100); mBlendFuncs[equation] = MakeSimpleFunctionDefinition( mSymbolTable, "ANGLE_blend_difference", result, {src, dst}); } break; case gl::BlendEquationType::Exclusion: // float ANGLE_blend_exclusion(float src, float dst) // { // return src + dst - (2.0f * src * dst); // } { TIntermSymbol *src = MakeVariable(mSymbolTable, "src", floatParamType); TIntermSymbol *dst = MakeVariable(mSymbolTable, "dst", floatParamType); // src + dst TIntermTyped *sum = new TIntermBinary(EOpAdd, src, dst); // src * dst TIntermTyped *mul = new TIntermBinary(EOpMul, src->deepCopy(), dst->deepCopy()); // 2 * src * dst TIntermTyped *mul2 = new TIntermBinary(EOpMul, mul, Float(2)); // src + dst - 2 * src * dst TIntermTyped *result = new TIntermBinary(EOpSub, sum, mul2); mBlendFuncs[equation] = MakeSimpleFunctionDefinition( mSymbolTable, "ANGLE_blend_exclusion", result, {src, dst}); } break; case gl::BlendEquationType::HslHue: // vec3 ANGLE_blend_hsl_hue(vec3 src, vec3 dst) // { // return ANGLE_set_lum_sat(src, dst, dst); // } { TIntermSymbol *src = MakeVariable(mSymbolTable, "src", vec3ParamType); TIntermSymbol *dst = MakeVariable(mSymbolTable, "dst", vec3ParamType); TIntermSequence args = {src, dst, dst->deepCopy()}; TIntermTyped *result = TIntermAggregate::CreateFunctionCall(*mSetLumSat->getFunction(), &args); mBlendFuncs[equation] = MakeSimpleFunctionDefinition( mSymbolTable, "ANGLE_blend_hsl_hue", result, {src, dst}); } break; case gl::BlendEquationType::HslSaturation: // vec3 ANGLE_blend_hsl_saturation(vec3 src, vec3 dst) // { // return ANGLE_set_lum_sat(dst, src, dst); // } { TIntermSymbol *src = MakeVariable(mSymbolTable, "src", vec3ParamType); TIntermSymbol *dst = MakeVariable(mSymbolTable, "dst", vec3ParamType); TIntermSequence args = {dst, src, dst->deepCopy()}; TIntermTyped *result = TIntermAggregate::CreateFunctionCall(*mSetLumSat->getFunction(), &args); mBlendFuncs[equation] = MakeSimpleFunctionDefinition( mSymbolTable, "ANGLE_blend_hsl_saturation", result, {src, dst}); } break; case gl::BlendEquationType::HslColor: // vec3 ANGLE_blend_hsl_color(vec3 src, vec3 dst) // { // return ANGLE_set_lum(src, dst); // } { TIntermSymbol *src = MakeVariable(mSymbolTable, "src", vec3ParamType); TIntermSymbol *dst = MakeVariable(mSymbolTable, "dst", vec3ParamType); TIntermSequence args = {src, dst}; TIntermTyped *result = TIntermAggregate::CreateFunctionCall(*mSetLum->getFunction(), &args); mBlendFuncs[equation] = MakeSimpleFunctionDefinition( mSymbolTable, "ANGLE_blend_hsl_color", result, {src, dst}); } break; case gl::BlendEquationType::HslLuminosity: // vec3 ANGLE_blend_hsl_luminosity(vec3 src, vec3 dst) // { // return ANGLE_set_lum(dst, src); // } { TIntermSymbol *src = MakeVariable(mSymbolTable, "src", vec3ParamType); TIntermSymbol *dst = MakeVariable(mSymbolTable, "dst", vec3ParamType); TIntermSequence args = {dst, src}; TIntermTyped *result = TIntermAggregate::CreateFunctionCall(*mSetLum->getFunction(), &args); mBlendFuncs[equation] = MakeSimpleFunctionDefinition( mSymbolTable, "ANGLE_blend_hsl_luminosity", result, {src, dst}); } break; default: // Only advanced blend equations are possible. UNREACHABLE(); } } } void Builder::insertGeneratedFunctions(TIntermBlock *root) { // Insert all generated functions in root. Since they are all inserted at index 0, HSL helpers // are inserted last, and in opposite order. for (TIntermFunctionDefinition *blendFunc : mBlendFuncs) { if (blendFunc != nullptr) { root->insertStatement(0, blendFunc); } } if (mMinv3 != nullptr) { root->insertStatement(0, mSetLumSat); root->insertStatement(0, mSetLum); root->insertStatement(0, mClipColor); root->insertStatement(0, mSatv3); root->insertStatement(0, mLumv3); root->insertStatement(0, mMaxv3); root->insertStatement(0, mMinv3); } } // On some platforms 1.0f is not returned even when the dividend and divisor have the same value. // In such cases emit 1.0f when the dividend and divisor are equal, else return the divide node TIntermTyped *Builder::divideFloatNode(TIntermTyped *dividend, TIntermTyped *divisor) { TIntermBinary *cond = new TIntermBinary(EOpEqual, dividend->deepCopy(), divisor->deepCopy()); TIntermBinary *divideExpr = new TIntermBinary(EOpDiv, dividend->deepCopy(), divisor->deepCopy()); return new TIntermTernary(cond, CreateFloatNode(1.0f, EbpHigh), divideExpr->deepCopy()); } TIntermSymbol *Builder::premultiplyAlpha(TIntermBlock *blendBlock, TIntermTyped *var, const char *name) { const TPrecision precision = mOutputVar->getType().getPrecision(); TType *vec3Type = new TType(EbtFloat, precision, EvqTemporary, 3); // symbol = vec3(0) // If alpha != 0, divide by alpha. Note that due to precision issues, component == alpha is // handled especially. This precision issue affects multiple vendors, and most drivers seem to // be carrying a similar workaround to pass the CTS test. TIntermTyped *alpha = new TIntermSwizzle(var, {3}); TIntermSymbol *symbol = MakeVariable(mSymbolTable, name, vec3Type); TIntermTyped *alphaNotZero = new TIntermBinary(EOpNotEqual, alpha, Float(0)); TIntermBlock *rgbDivAlphaBlock = new TIntermBlock; constexpr int kColorChannels = 3; // For each component: // symbol.x = (var.x == var.w) ? 1.0 : var.x / var.w for (int index = 0; index < kColorChannels; index++) { TIntermTyped *divideNode = divideFloatNode(new TIntermSwizzle(var, {index}), alpha); TIntermBinary *assignDivideNode = new TIntermBinary( EOpAssign, new TIntermSwizzle(symbol->deepCopy(), {index}), divideNode); rgbDivAlphaBlock->appendStatement(assignDivideNode); } TIntermIfElse *ifBlock = new TIntermIfElse(alphaNotZero, rgbDivAlphaBlock, nullptr); blendBlock->appendStatement( CreateTempInitDeclarationNode(&symbol->variable(), CreateZeroNode(*vec3Type))); blendBlock->appendStatement(ifBlock); return symbol; } TIntermTyped *GetFirstElementIfArray(TIntermTyped *var) { TIntermTyped *element = var; while (element->getType().isArray()) { element = new TIntermBinary(EOpIndexDirect, element, CreateIndexNode(0)); } return element; } void Builder::generatePreamble(TIntermBlock *blendBlock) { // Use subpassLoad to read from the input attachment const TPrecision precision = mOutputVar->getType().getPrecision(); TType *vec4Type = new TType(EbtFloat, precision, EvqTemporary, 4); TIntermSymbol *subpassInputData = MakeVariable(mSymbolTable, "ANGLELastFragData", vec4Type); // Initialize it with subpassLoad() result. // TODO: support interaction with multisampled framebuffers. For example, the sample ID needs // to be provided to the built-in call here. http://anglebug.com/6195 TIntermSequence subpassArguments = {new TIntermSymbol(mSubpassInputVar)}; TIntermTyped *subpassLoadFuncCall = CreateBuiltInFunctionCallNode( "subpassLoad", &subpassArguments, *mSymbolTable, kESSLInternalBackendBuiltIns); blendBlock->appendStatement( CreateTempInitDeclarationNode(&subpassInputData->variable(), subpassLoadFuncCall)); // Get element 0 of the output, if array. TIntermTyped *output = GetFirstElementIfArray(new TIntermSymbol(mOutputVar)); // Expand output to vec4, if not already. uint32_t vecSize = mOutputVar->getType().getNominalSize(); if (vecSize < 4) { TIntermSequence vec4Args = {output}; for (uint32_t channel = vecSize; channel < 3; ++channel) { vec4Args.push_back(Float(0)); } vec4Args.push_back(Float(1)); output = TIntermAggregate::CreateConstructor(*vec4Type, &vec4Args); } // Premultiply src and dst. mSrc = premultiplyAlpha(blendBlock, output, "ANGLE_blend_src"); mDst = premultiplyAlpha(blendBlock, subpassInputData, "ANGLE_blend_dst"); // Calculate the p coefficients: TIntermTyped *srcAlpha = new TIntermSwizzle(output->deepCopy(), {3}); TIntermTyped *dstAlpha = new TIntermSwizzle(subpassInputData->deepCopy(), {3}); // As * Ad TIntermTyped *AsTimesAd = new TIntermBinary(EOpMul, srcAlpha, dstAlpha); // As * (1. - Ad) TIntermTyped *oneMinusAd = new TIntermBinary(EOpSub, Float(1), dstAlpha->deepCopy()); TIntermTyped *AsTimesOneMinusAd = new TIntermBinary(EOpMul, srcAlpha->deepCopy(), oneMinusAd); // Ad * (1. - As) TIntermTyped *oneMinusAs = new TIntermBinary(EOpSub, Float(1), srcAlpha->deepCopy()); TIntermTyped *AdTimesOneMinusAs = new TIntermBinary(EOpMul, dstAlpha->deepCopy(), oneMinusAs); mP0 = MakeVariable(mSymbolTable, "ANGLE_blend_p0", &srcAlpha->getType()); mP1 = MakeVariable(mSymbolTable, "ANGLE_blend_p1", &srcAlpha->getType()); mP2 = MakeVariable(mSymbolTable, "ANGLE_blend_p2", &srcAlpha->getType()); blendBlock->appendStatement(CreateTempInitDeclarationNode(&mP0->variable(), AsTimesAd)); blendBlock->appendStatement(CreateTempInitDeclarationNode(&mP1->variable(), AsTimesOneMinusAd)); blendBlock->appendStatement(CreateTempInitDeclarationNode(&mP2->variable(), AdTimesOneMinusAs)); } void Builder::generateEquationSwitch(TIntermBlock *blendBlock) { const TPrecision precision = mOutputVar->getType().getPrecision(); TType *vec3Type = new TType(EbtFloat, precision, EvqTemporary, 3); TType *vec4Type = new TType(EbtFloat, precision, EvqTemporary, 4); // The following code is generated: // // vec3 f; // swtich (equation) // { // case A: // f = ANGLE_blend_a(..); // break; // case B: // f = ANGLE_blend_b(..); // break; // ... // } // // vec3 rgb = f * p0 + src * p1 + dst * p2 // float a = p0 + p1 + p2 // // output = vec4(rgb, a); TIntermSymbol *f = MakeVariable(mSymbolTable, "ANGLE_f", vec3Type); blendBlock->appendStatement(CreateTempDeclarationNode(&f->variable())); TIntermBlock *switchBody = new TIntermBlock; gl::BlendEquationBitSet enabledBlendEquations(mAdvancedBlendEquations.bits()); for (gl::BlendEquationType equation : enabledBlendEquations) { switchBody->appendStatement( new TIntermCase(CreateUIntNode(static_cast(equation)))); // HSL equations call the blend function with all channels. Non-HSL equations call it per // component. if (equation < gl::BlendEquationType::HslHue) { TIntermSequence constructorArgs; for (int channel = 0; channel < 3; ++channel) { TIntermTyped *srcChannel = new TIntermSwizzle(mSrc->deepCopy(), {channel}); TIntermTyped *dstChannel = new TIntermSwizzle(mDst->deepCopy(), {channel}); TIntermSequence args = {srcChannel, dstChannel}; constructorArgs.push_back(TIntermAggregate::CreateFunctionCall( *mBlendFuncs[equation]->getFunction(), &args)); } TIntermTyped *constructor = TIntermAggregate::CreateConstructor(*vec3Type, &constructorArgs); switchBody->appendStatement(new TIntermBinary(EOpAssign, f->deepCopy(), constructor)); } else { TIntermSequence args = {mSrc->deepCopy(), mDst->deepCopy()}; TIntermTyped *blendCall = TIntermAggregate::CreateFunctionCall(*mBlendFuncs[equation]->getFunction(), &args); switchBody->appendStatement(new TIntermBinary(EOpAssign, f->deepCopy(), blendCall)); } switchBody->appendStatement(new TIntermBranch(EOpBreak, nullptr)); } // A driver uniform is used to communicate the blend equation to use. TIntermTyped *equationUniform = mDriverUniforms->getAdvancedBlendEquation(); blendBlock->appendStatement(new TIntermSwitch(equationUniform, switchBody)); // Calculate the final blend according to the following formula: // // RGB = f(src, dst) * p0 + src * p1 + dst * p2 // A = p0 + p1 + p2 // f * p0 TIntermTyped *fTimesP0 = new TIntermBinary(EOpVectorTimesScalar, f, mP0); // src * p1 TIntermTyped *srcTimesP1 = new TIntermBinary(EOpVectorTimesScalar, mSrc, mP1); // dst * p2 TIntermTyped *dstTimesP2 = new TIntermBinary(EOpVectorTimesScalar, mDst, mP2); // f * p0 + src * p1 + dst * p2 TIntermTyped *rgb = new TIntermBinary(EOpAdd, new TIntermBinary(EOpAdd, fTimesP0, srcTimesP1), dstTimesP2); // p0 + p1 + p2 TIntermTyped *a = new TIntermBinary( EOpAdd, new TIntermBinary(EOpAdd, mP0->deepCopy(), mP1->deepCopy()), mP2->deepCopy()); // Intialize the output with vec4(RGB, A) TIntermSequence rgbaArgs = {rgb, a}; TIntermTyped *blendResult = TIntermAggregate::CreateConstructor(*vec4Type, &rgbaArgs); // If the output has fewer than four channels, swizzle the results uint32_t vecSize = mOutputVar->getType().getNominalSize(); if (vecSize < 4) { TVector swizzle = {0, 1, 2, 3}; swizzle.resize(vecSize); blendResult = new TIntermSwizzle(blendResult, swizzle); } TIntermTyped *output = GetFirstElementIfArray(new TIntermSymbol(mOutputVar)); blendBlock->appendStatement(new TIntermBinary(EOpAssign, output, blendResult)); } } // anonymous namespace bool EmulateAdvancedBlendEquations(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable, const DriverUniform *driverUniforms, std::vector *uniforms, const AdvancedBlendEquations &advancedBlendEquations) { Builder builder(compiler, symbolTable, driverUniforms, uniforms, advancedBlendEquations); return builder.build(root); } // namespace } // namespace sh