blob: 4a4294cff831771886cf95e352a46f41ba6d1c63 [file] [log] [blame]
//
// 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/vulkan/EmulateAdvancedBlendEquations.h"
#include <map>
#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,
const ShCompileOptions &compileOptions,
TSymbolTable *symbolTable,
const DriverUniform *driverUniforms,
std::vector<ShaderVariable> *uniforms,
const AdvancedBlendEquations &advancedBlendEquations)
: mCompiler(compiler),
mCompileOptions(compileOptions),
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;
const ShCompileOptions &mCompileOptions;
TSymbolTable *mSymbolTable;
const DriverUniform *mDriverUniforms;
std::vector<ShaderVariable> *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<gl::BlendEquationType, TIntermFunctionDefinition *> 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<const TVariable *> &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<TIntermSymbol *> &args)
{
TVector<const TVariable *> 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<float, 3> 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 assign symbol based on precisionSafeDivision compile option.
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;
if (mCompileOptions.precisionSafeDivision)
{
// 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);
}
}
else
{
// symbol = rgb/alpha
TIntermTyped *rgb = new TIntermSwizzle(var->deepCopy(), {0, 1, 2});
TIntermTyped *rgbDivAlpha = new TIntermBinary(EOpDiv, rgb, alpha->deepCopy());
rgbDivAlphaBlock->appendStatement(
new TIntermBinary(EOpAssign, symbol->deepCopy(), rgbDivAlpha));
}
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<uint32_t>(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<int> 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,
const ShCompileOptions &compileOptions,
TIntermBlock *root,
TSymbolTable *symbolTable,
const DriverUniform *driverUniforms,
std::vector<ShaderVariable> *uniforms,
const AdvancedBlendEquations &advancedBlendEquations)
{
Builder builder(compiler, compileOptions, symbolTable, driverUniforms, uniforms,
advancedBlendEquations);
return builder.build(root);
} // namespace
} // namespace sh