blob: 529429485af6727aaf66835e7b25b6fe25c0285e [file] [log] [blame]
//
// Copyright 2002 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.
//
#include "compiler/translator/OutputHLSL.h"
#include <stdio.h>
#include <algorithm>
#include <cfloat>
#include "common/angleutils.h"
#include "common/debug.h"
#include "common/utilities.h"
#include "compiler/translator/AtomicCounterFunctionHLSL.h"
#include "compiler/translator/BuiltInFunctionEmulator.h"
#include "compiler/translator/BuiltInFunctionEmulatorHLSL.h"
#include "compiler/translator/ImageFunctionHLSL.h"
#include "compiler/translator/InfoSink.h"
#include "compiler/translator/ResourcesHLSL.h"
#include "compiler/translator/StructureHLSL.h"
#include "compiler/translator/TextureFunctionHLSL.h"
#include "compiler/translator/TranslatorHLSL.h"
#include "compiler/translator/UtilsHLSL.h"
#include "compiler/translator/blocklayout.h"
#include "compiler/translator/tree_ops/RemoveSwitchFallThrough.h"
#include "compiler/translator/tree_util/FindSymbolNode.h"
#include "compiler/translator/tree_util/NodeSearch.h"
#include "compiler/translator/util.h"
namespace sh
{
namespace
{
constexpr const char kImage2DFunctionString[] = "// @@ IMAGE2D DECLARATION FUNCTION STRING @@";
TString ArrayHelperFunctionName(const char *prefix, const TType &type)
{
TStringStream fnName = sh::InitializeStream<TStringStream>();
fnName << prefix << "_";
if (type.isArray())
{
for (unsigned int arraySize : *type.getArraySizes())
{
fnName << arraySize << "_";
}
}
fnName << TypeString(type);
return fnName.str();
}
bool IsDeclarationWrittenOut(TIntermDeclaration *node)
{
TIntermSequence *sequence = node->getSequence();
TIntermTyped *variable = (*sequence)[0]->getAsTyped();
ASSERT(sequence->size() == 1);
ASSERT(variable);
return (variable->getQualifier() == EvqTemporary || variable->getQualifier() == EvqGlobal ||
variable->getQualifier() == EvqConst || variable->getQualifier() == EvqShared);
}
bool IsInStd140UniformBlock(TIntermTyped *node)
{
TIntermBinary *binaryNode = node->getAsBinaryNode();
if (binaryNode)
{
return IsInStd140UniformBlock(binaryNode->getLeft());
}
const TType &type = node->getType();
if (type.getQualifier() == EvqUniform)
{
// determine if we are in the standard layout
const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock();
if (interfaceBlock)
{
return (interfaceBlock->blockStorage() == EbsStd140);
}
}
return false;
}
const char *GetHLSLAtomicFunctionStringAndLeftParenthesis(TOperator op)
{
switch (op)
{
case EOpAtomicAdd:
return "InterlockedAdd(";
case EOpAtomicMin:
return "InterlockedMin(";
case EOpAtomicMax:
return "InterlockedMax(";
case EOpAtomicAnd:
return "InterlockedAnd(";
case EOpAtomicOr:
return "InterlockedOr(";
case EOpAtomicXor:
return "InterlockedXor(";
case EOpAtomicExchange:
return "InterlockedExchange(";
case EOpAtomicCompSwap:
return "InterlockedCompareExchange(";
default:
UNREACHABLE();
return "";
}
}
bool IsAtomicFunctionForSharedVariableDirectAssign(const TIntermBinary &node)
{
TIntermAggregate *aggregateNode = node.getRight()->getAsAggregate();
if (aggregateNode == nullptr)
{
return false;
}
if (node.getOp() == EOpAssign && IsAtomicFunction(aggregateNode->getOp()))
{
return !IsInShaderStorageBlock((*aggregateNode->getSequence())[0]->getAsTyped());
}
return false;
}
const char *kZeros = "_ANGLE_ZEROS_";
constexpr int kZeroCount = 256;
std::string DefineZeroArray()
{
std::stringstream ss = sh::InitializeStream<std::stringstream>();
// For 'static', if the declaration does not include an initializer, the value is set to zero.
// https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/dx-graphics-hlsl-variable-syntax
ss << "static uint " << kZeros << "[" << kZeroCount << "];\n";
return ss.str();
}
std::string GetZeroInitializer(size_t size)
{
std::stringstream ss = sh::InitializeStream<std::stringstream>();
size_t quotient = size / kZeroCount;
size_t reminder = size % kZeroCount;
for (size_t i = 0; i < quotient; ++i)
{
if (i != 0)
{
ss << ", ";
}
ss << kZeros;
}
for (size_t i = 0; i < reminder; ++i)
{
if (quotient != 0 || i != 0)
{
ss << ", ";
}
ss << "0";
}
return ss.str();
}
} // anonymous namespace
TReferencedBlock::TReferencedBlock(const TInterfaceBlock *aBlock,
const TVariable *aInstanceVariable)
: block(aBlock), instanceVariable(aInstanceVariable)
{}
bool OutputHLSL::needStructMapping(TIntermTyped *node)
{
ASSERT(node->getBasicType() == EbtStruct);
for (unsigned int n = 0u; getAncestorNode(n) != nullptr; ++n)
{
TIntermNode *ancestor = getAncestorNode(n);
const TIntermBinary *ancestorBinary = ancestor->getAsBinaryNode();
if (ancestorBinary)
{
switch (ancestorBinary->getOp())
{
case EOpIndexDirectStruct:
{
const TStructure *structure = ancestorBinary->getLeft()->getType().getStruct();
const TIntermConstantUnion *index =
ancestorBinary->getRight()->getAsConstantUnion();
const TField *field = structure->fields()[index->getIConst(0)];
if (field->type()->getStruct() == nullptr)
{
return false;
}
break;
}
case EOpIndexDirect:
case EOpIndexIndirect:
break;
default:
return true;
}
}
else
{
const TIntermAggregate *ancestorAggregate = ancestor->getAsAggregate();
if (ancestorAggregate)
{
return true;
}
return false;
}
}
return true;
}
void OutputHLSL::writeFloat(TInfoSinkBase &out, float f)
{
// This is known not to work for NaN on all drivers but make the best effort to output NaNs
// regardless.
if ((gl::isInf(f) || gl::isNaN(f)) && mShaderVersion >= 300 &&
mOutputType == SH_HLSL_4_1_OUTPUT)
{
out << "asfloat(" << gl::bitCast<uint32_t>(f) << "u)";
}
else
{
out << std::min(FLT_MAX, std::max(-FLT_MAX, f));
}
}
void OutputHLSL::writeSingleConstant(TInfoSinkBase &out, const TConstantUnion *const constUnion)
{
ASSERT(constUnion != nullptr);
switch (constUnion->getType())
{
case EbtFloat:
writeFloat(out, constUnion->getFConst());
break;
case EbtInt:
out << constUnion->getIConst();
break;
case EbtUInt:
out << constUnion->getUConst();
break;
case EbtBool:
out << constUnion->getBConst();
break;
default:
UNREACHABLE();
}
}
const TConstantUnion *OutputHLSL::writeConstantUnionArray(TInfoSinkBase &out,
const TConstantUnion *const constUnion,
const size_t size)
{
const TConstantUnion *constUnionIterated = constUnion;
for (size_t i = 0; i < size; i++, constUnionIterated++)
{
writeSingleConstant(out, constUnionIterated);
if (i != size - 1)
{
out << ", ";
}
}
return constUnionIterated;
}
OutputHLSL::OutputHLSL(sh::GLenum shaderType,
ShShaderSpec shaderSpec,
int shaderVersion,
const TExtensionBehavior &extensionBehavior,
const char *sourcePath,
ShShaderOutput outputType,
int numRenderTargets,
int maxDualSourceDrawBuffers,
const std::vector<ShaderVariable> &uniforms,
ShCompileOptions compileOptions,
sh::WorkGroupSize workGroupSize,
TSymbolTable *symbolTable,
PerformanceDiagnostics *perfDiagnostics,
const std::vector<InterfaceBlock> &shaderStorageBlocks)
: TIntermTraverser(true, true, true, symbolTable),
mShaderType(shaderType),
mShaderSpec(shaderSpec),
mShaderVersion(shaderVersion),
mExtensionBehavior(extensionBehavior),
mSourcePath(sourcePath),
mOutputType(outputType),
mCompileOptions(compileOptions),
mInsideFunction(false),
mInsideMain(false),
mNumRenderTargets(numRenderTargets),
mMaxDualSourceDrawBuffers(maxDualSourceDrawBuffers),
mCurrentFunctionMetadata(nullptr),
mWorkGroupSize(workGroupSize),
mPerfDiagnostics(perfDiagnostics),
mNeedStructMapping(false)
{
mUsesFragColor = false;
mUsesFragData = false;
mUsesDepthRange = false;
mUsesFragCoord = false;
mUsesPointCoord = false;
mUsesFrontFacing = false;
mUsesHelperInvocation = false;
mUsesPointSize = false;
mUsesInstanceID = false;
mHasMultiviewExtensionEnabled =
IsExtensionEnabled(mExtensionBehavior, TExtension::OVR_multiview) ||
IsExtensionEnabled(mExtensionBehavior, TExtension::OVR_multiview2);
mUsesViewID = false;
mUsesVertexID = false;
mUsesFragDepth = false;
mUsesNumWorkGroups = false;
mUsesWorkGroupID = false;
mUsesLocalInvocationID = false;
mUsesGlobalInvocationID = false;
mUsesLocalInvocationIndex = false;
mUsesXor = false;
mUsesDiscardRewriting = false;
mUsesNestedBreak = false;
mRequiresIEEEStrictCompiling = false;
mUseZeroArray = false;
mUsesSecondaryColor = false;
mUniqueIndex = 0;
mOutputLod0Function = false;
mInsideDiscontinuousLoop = false;
mNestedLoopDepth = 0;
mExcessiveLoopIndex = nullptr;
mStructureHLSL = new StructureHLSL;
mTextureFunctionHLSL = new TextureFunctionHLSL;
mImageFunctionHLSL = new ImageFunctionHLSL;
mAtomicCounterFunctionHLSL =
new AtomicCounterFunctionHLSL((compileOptions & SH_FORCE_ATOMIC_VALUE_RESOLUTION) != 0);
unsigned int firstUniformRegister =
((compileOptions & SH_SKIP_D3D_CONSTANT_REGISTER_ZERO) != 0) ? 1u : 0u;
mResourcesHLSL = new ResourcesHLSL(mStructureHLSL, outputType, uniforms, firstUniformRegister);
if (mOutputType == SH_HLSL_3_0_OUTPUT)
{
// Fragment shaders need dx_DepthRange, dx_ViewCoords and dx_DepthFront.
// Vertex shaders need a slightly different set: dx_DepthRange, dx_ViewCoords and
// dx_ViewAdjust.
// In both cases total 3 uniform registers need to be reserved.
mResourcesHLSL->reserveUniformRegisters(3);
}
// Reserve registers for the default uniform block and driver constants
mResourcesHLSL->reserveUniformBlockRegisters(2);
mSSBOOutputHLSL =
new ShaderStorageBlockOutputHLSL(this, symbolTable, mResourcesHLSL, shaderStorageBlocks);
}
OutputHLSL::~OutputHLSL()
{
SafeDelete(mSSBOOutputHLSL);
SafeDelete(mStructureHLSL);
SafeDelete(mResourcesHLSL);
SafeDelete(mTextureFunctionHLSL);
SafeDelete(mImageFunctionHLSL);
SafeDelete(mAtomicCounterFunctionHLSL);
for (auto &eqFunction : mStructEqualityFunctions)
{
SafeDelete(eqFunction);
}
for (auto &eqFunction : mArrayEqualityFunctions)
{
SafeDelete(eqFunction);
}
}
void OutputHLSL::output(TIntermNode *treeRoot, TInfoSinkBase &objSink)
{
BuiltInFunctionEmulator builtInFunctionEmulator;
InitBuiltInFunctionEmulatorForHLSL(&builtInFunctionEmulator);
if ((mCompileOptions & SH_EMULATE_ISNAN_FLOAT_FUNCTION) != 0)
{
InitBuiltInIsnanFunctionEmulatorForHLSLWorkarounds(&builtInFunctionEmulator,
mShaderVersion);
}
builtInFunctionEmulator.markBuiltInFunctionsForEmulation(treeRoot);
// Now that we are done changing the AST, do the analyses need for HLSL generation
CallDAG::InitResult success = mCallDag.init(treeRoot, nullptr);
ASSERT(success == CallDAG::INITDAG_SUCCESS);
mASTMetadataList = CreateASTMetadataHLSL(treeRoot, mCallDag);
const std::vector<MappedStruct> std140Structs = FlagStd140Structs(treeRoot);
// TODO(oetuaho): The std140Structs could be filtered based on which ones actually get used in
// the shader code. When we add shader storage blocks we might also consider an alternative
// solution, since the struct mapping won't work very well for shader storage blocks.
// Output the body and footer first to determine what has to go in the header
mInfoSinkStack.push(&mBody);
treeRoot->traverse(this);
mInfoSinkStack.pop();
mInfoSinkStack.push(&mFooter);
mInfoSinkStack.pop();
mInfoSinkStack.push(&mHeader);
header(mHeader, std140Structs, &builtInFunctionEmulator);
mInfoSinkStack.pop();
objSink << mHeader.c_str();
objSink << mBody.c_str();
objSink << mFooter.c_str();
builtInFunctionEmulator.cleanup();
}
const std::map<std::string, unsigned int> &OutputHLSL::getShaderStorageBlockRegisterMap() const
{
return mResourcesHLSL->getShaderStorageBlockRegisterMap();
}
const std::map<std::string, unsigned int> &OutputHLSL::getUniformBlockRegisterMap() const
{
return mResourcesHLSL->getUniformBlockRegisterMap();
}
const std::map<std::string, unsigned int> &OutputHLSL::getUniformRegisterMap() const
{
return mResourcesHLSL->getUniformRegisterMap();
}
unsigned int OutputHLSL::getReadonlyImage2DRegisterIndex() const
{
return mResourcesHLSL->getReadonlyImage2DRegisterIndex();
}
unsigned int OutputHLSL::getImage2DRegisterIndex() const
{
return mResourcesHLSL->getImage2DRegisterIndex();
}
const std::set<std::string> &OutputHLSL::getUsedImage2DFunctionNames() const
{
return mImageFunctionHLSL->getUsedImage2DFunctionNames();
}
TString OutputHLSL::structInitializerString(int indent,
const TType &type,
const TString &name) const
{
TString init;
TString indentString;
for (int spaces = 0; spaces < indent; spaces++)
{
indentString += " ";
}
if (type.isArray())
{
init += indentString + "{\n";
for (unsigned int arrayIndex = 0u; arrayIndex < type.getOutermostArraySize(); ++arrayIndex)
{
TStringStream indexedString = sh::InitializeStream<TStringStream>();
indexedString << name << "[" << arrayIndex << "]";
TType elementType = type;
elementType.toArrayElementType();
init += structInitializerString(indent + 1, elementType, indexedString.str());
if (arrayIndex < type.getOutermostArraySize() - 1)
{
init += ",";
}
init += "\n";
}
init += indentString + "}";
}
else if (type.getBasicType() == EbtStruct)
{
init += indentString + "{\n";
const TStructure &structure = *type.getStruct();
const TFieldList &fields = structure.fields();
for (unsigned int fieldIndex = 0; fieldIndex < fields.size(); fieldIndex++)
{
const TField &field = *fields[fieldIndex];
const TString &fieldName = name + "." + Decorate(field.name());
const TType &fieldType = *field.type();
init += structInitializerString(indent + 1, fieldType, fieldName);
if (fieldIndex < fields.size() - 1)
{
init += ",";
}
init += "\n";
}
init += indentString + "}";
}
else
{
init += indentString + name;
}
return init;
}
TString OutputHLSL::generateStructMapping(const std::vector<MappedStruct> &std140Structs) const
{
TString mappedStructs;
for (auto &mappedStruct : std140Structs)
{
const TInterfaceBlock *interfaceBlock =
mappedStruct.blockDeclarator->getType().getInterfaceBlock();
TQualifier qualifier = mappedStruct.blockDeclarator->getType().getQualifier();
switch (qualifier)
{
case EvqUniform:
if (mReferencedUniformBlocks.count(interfaceBlock->uniqueId().get()) == 0)
{
continue;
}
break;
case EvqBuffer:
continue;
default:
UNREACHABLE();
return mappedStructs;
}
unsigned int instanceCount = 1u;
bool isInstanceArray = mappedStruct.blockDeclarator->isArray();
if (isInstanceArray)
{
instanceCount = mappedStruct.blockDeclarator->getOutermostArraySize();
}
for (unsigned int instanceArrayIndex = 0; instanceArrayIndex < instanceCount;
++instanceArrayIndex)
{
TString originalName;
TString mappedName("map");
if (mappedStruct.blockDeclarator->variable().symbolType() != SymbolType::Empty)
{
const ImmutableString &instanceName =
mappedStruct.blockDeclarator->variable().name();
unsigned int instanceStringArrayIndex = GL_INVALID_INDEX;
if (isInstanceArray)
instanceStringArrayIndex = instanceArrayIndex;
TString instanceString = mResourcesHLSL->InterfaceBlockInstanceString(
instanceName, instanceStringArrayIndex);
originalName += instanceString;
mappedName += instanceString;
originalName += ".";
mappedName += "_";
}
TString fieldName = Decorate(mappedStruct.field->name());
originalName += fieldName;
mappedName += fieldName;
TType *structType = mappedStruct.field->type();
mappedStructs +=
"static " + Decorate(structType->getStruct()->name()) + " " + mappedName;
if (structType->isArray())
{
mappedStructs += ArrayString(*mappedStruct.field->type()).data();
}
mappedStructs += " =\n";
mappedStructs += structInitializerString(0, *structType, originalName);
mappedStructs += ";\n";
}
}
return mappedStructs;
}
void OutputHLSL::writeReferencedAttributes(TInfoSinkBase &out) const
{
for (const auto &attribute : mReferencedAttributes)
{
const TType &type = attribute.second->getType();
const ImmutableString &name = attribute.second->name();
out << "static " << TypeString(type) << " " << Decorate(name) << ArrayString(type) << " = "
<< zeroInitializer(type) << ";\n";
}
}
void OutputHLSL::writeReferencedVaryings(TInfoSinkBase &out) const
{
for (const auto &varying : mReferencedVaryings)
{
const TType &type = varying.second->getType();
// Program linking depends on this exact format
out << "static " << InterpolationString(type.getQualifier()) << " " << TypeString(type)
<< " " << DecorateVariableIfNeeded(*varying.second) << ArrayString(type) << " = "
<< zeroInitializer(type) << ";\n";
}
}
void OutputHLSL::header(TInfoSinkBase &out,
const std::vector<MappedStruct> &std140Structs,
const BuiltInFunctionEmulator *builtInFunctionEmulator) const
{
TString mappedStructs;
if (mNeedStructMapping)
{
mappedStructs = generateStructMapping(std140Structs);
}
out << mStructureHLSL->structsHeader();
mResourcesHLSL->uniformsHeader(out, mOutputType, mReferencedUniforms, mSymbolTable);
out << mResourcesHLSL->uniformBlocksHeader(mReferencedUniformBlocks);
mSSBOOutputHLSL->writeShaderStorageBlocksHeader(out);
if (!mEqualityFunctions.empty())
{
out << "\n// Equality functions\n\n";
for (const auto &eqFunction : mEqualityFunctions)
{
out << eqFunction->functionDefinition << "\n";
}
}
if (!mArrayAssignmentFunctions.empty())
{
out << "\n// Assignment functions\n\n";
for (const auto &assignmentFunction : mArrayAssignmentFunctions)
{
out << assignmentFunction.functionDefinition << "\n";
}
}
if (!mArrayConstructIntoFunctions.empty())
{
out << "\n// Array constructor functions\n\n";
for (const auto &constructIntoFunction : mArrayConstructIntoFunctions)
{
out << constructIntoFunction.functionDefinition << "\n";
}
}
if (mUsesDiscardRewriting)
{
out << "#define ANGLE_USES_DISCARD_REWRITING\n";
}
if (mUsesNestedBreak)
{
out << "#define ANGLE_USES_NESTED_BREAK\n";
}
if (mRequiresIEEEStrictCompiling)
{
out << "#define ANGLE_REQUIRES_IEEE_STRICT_COMPILING\n";
}
out << "#ifdef ANGLE_ENABLE_LOOP_FLATTEN\n"
"#define LOOP [loop]\n"
"#define FLATTEN [flatten]\n"
"#else\n"
"#define LOOP\n"
"#define FLATTEN\n"
"#endif\n";
// array stride for atomic counter buffers is always 4 per original extension
// ARB_shader_atomic_counters and discussion on
// https://github.com/KhronosGroup/OpenGL-API/issues/5
out << "\n#define ATOMIC_COUNTER_ARRAY_STRIDE 4\n\n";
if (mUseZeroArray)
{
out << DefineZeroArray() << "\n";
}
if (mShaderType == GL_FRAGMENT_SHADER)
{
const bool usingMRTExtension =
IsExtensionEnabled(mExtensionBehavior, TExtension::EXT_draw_buffers);
const bool usingBFEExtension =
IsExtensionEnabled(mExtensionBehavior, TExtension::EXT_blend_func_extended);
out << "// Varyings\n";
writeReferencedVaryings(out);
out << "\n";
if ((IsDesktopGLSpec(mShaderSpec) && mShaderVersion >= 130) ||
(!IsDesktopGLSpec(mShaderSpec) && mShaderVersion >= 300))
{
for (const auto &outputVariable : mReferencedOutputVariables)
{
const ImmutableString &variableName = outputVariable.second->name();
const TType &variableType = outputVariable.second->getType();
out << "static " << TypeString(variableType) << " out_" << variableName
<< ArrayString(variableType) << " = " << zeroInitializer(variableType) << ";\n";
}
}
else
{
const unsigned int numColorValues = usingMRTExtension ? mNumRenderTargets : 1;
out << "static float4 gl_Color[" << numColorValues
<< "] =\n"
"{\n";
for (unsigned int i = 0; i < numColorValues; i++)
{
out << " float4(0, 0, 0, 0)";
if (i + 1 != numColorValues)
{
out << ",";
}
out << "\n";
}
out << "};\n";
if (usingBFEExtension && mUsesSecondaryColor)
{
out << "static float4 gl_SecondaryColor[" << mMaxDualSourceDrawBuffers
<< "] = \n"
"{\n";
for (int i = 0; i < mMaxDualSourceDrawBuffers; i++)
{
out << " float4(0, 0, 0, 0)";
if (i + 1 != mMaxDualSourceDrawBuffers)
{
out << ",";
}
out << "\n";
}
out << "};\n";
}
}
if (mUsesFragDepth)
{
out << "static float gl_Depth = 0.0;\n";
}
if (mUsesFragCoord)
{
out << "static float4 gl_FragCoord = float4(0, 0, 0, 0);\n";
}
if (mUsesPointCoord)
{
out << "static float2 gl_PointCoord = float2(0.5, 0.5);\n";
}
if (mUsesFrontFacing)
{
out << "static bool gl_FrontFacing = false;\n";
}
if (mUsesHelperInvocation)
{
out << "static bool gl_HelperInvocation = false;\n";
}
out << "\n";
if (mUsesDepthRange)
{
out << "struct gl_DepthRangeParameters\n"
"{\n"
" float near;\n"
" float far;\n"
" float diff;\n"
"};\n"
"\n";
}
if (mOutputType == SH_HLSL_4_1_OUTPUT || mOutputType == SH_HLSL_4_0_FL9_3_OUTPUT)
{
out << "cbuffer DriverConstants : register(b1)\n"
"{\n";
if (mUsesDepthRange)
{
out << " float3 dx_DepthRange : packoffset(c0);\n";
}
if (mUsesFragCoord)
{
out << " float4 dx_ViewCoords : packoffset(c1);\n";
}
if (mUsesFragCoord || mUsesFrontFacing)
{
out << " float3 dx_DepthFront : packoffset(c2);\n";
}
if (mUsesFragCoord)
{
// dx_ViewScale is only used in the fragment shader to correct
// the value for glFragCoord if necessary
out << " float2 dx_ViewScale : packoffset(c3);\n";
}
if (mHasMultiviewExtensionEnabled)
{
// We have to add a value which we can use to keep track of which multi-view code
// path is to be selected in the GS.
out << " float multiviewSelectViewportIndex : packoffset(c3.z);\n";
}
if (mOutputType == SH_HLSL_4_1_OUTPUT)
{
mResourcesHLSL->samplerMetadataUniforms(out, 4);
}
out << "};\n";
}
else
{
if (mUsesDepthRange)
{
out << "uniform float3 dx_DepthRange : register(c0);";
}
if (mUsesFragCoord)
{
out << "uniform float4 dx_ViewCoords : register(c1);\n";
}
if (mUsesFragCoord || mUsesFrontFacing)
{
out << "uniform float3 dx_DepthFront : register(c2);\n";
}
}
out << "\n";
if (mUsesDepthRange)
{
out << "static gl_DepthRangeParameters gl_DepthRange = {dx_DepthRange.x, "
"dx_DepthRange.y, dx_DepthRange.z};\n"
"\n";
}
if (usingMRTExtension && mNumRenderTargets > 1)
{
out << "#define GL_USES_MRT\n";
}
if (mUsesFragColor)
{
out << "#define GL_USES_FRAG_COLOR\n";
}
if (mUsesFragData)
{
out << "#define GL_USES_FRAG_DATA\n";
}
if (mShaderVersion < 300 && usingBFEExtension && mUsesSecondaryColor)
{
out << "#define GL_USES_SECONDARY_COLOR\n";
}
}
else if (mShaderType == GL_VERTEX_SHADER)
{
out << "// Attributes\n";
writeReferencedAttributes(out);
out << "\n"
"static float4 gl_Position = float4(0, 0, 0, 0);\n";
if (mUsesPointSize)
{
out << "static float gl_PointSize = float(1);\n";
}
if (mUsesInstanceID)
{
out << "static int gl_InstanceID;";
}
if (mUsesVertexID)
{
out << "static int gl_VertexID;";
}
out << "\n"
"// Varyings\n";
writeReferencedVaryings(out);
out << "\n";
if (mUsesDepthRange)
{
out << "struct gl_DepthRangeParameters\n"
"{\n"
" float near;\n"
" float far;\n"
" float diff;\n"
"};\n"
"\n";
}
if (mOutputType == SH_HLSL_4_1_OUTPUT || mOutputType == SH_HLSL_4_0_FL9_3_OUTPUT)
{
out << "cbuffer DriverConstants : register(b1)\n"
"{\n";
if (mUsesDepthRange)
{
out << " float3 dx_DepthRange : packoffset(c0);\n";
}
// dx_ViewAdjust and dx_ViewCoords will only be used in Feature Level 9
// shaders. However, we declare it for all shaders (including Feature Level 10+).
// The bytecode is the same whether we declare it or not, since D3DCompiler removes it
// if it's unused.
out << " float4 dx_ViewAdjust : packoffset(c1);\n";
out << " float2 dx_ViewCoords : packoffset(c2);\n";
out << " float2 dx_ViewScale : packoffset(c3);\n";
if (mHasMultiviewExtensionEnabled)
{
// We have to add a value which we can use to keep track of which multi-view code
// path is to be selected in the GS.
out << " float multiviewSelectViewportIndex : packoffset(c3.z);\n";
}
if (mOutputType == SH_HLSL_4_1_OUTPUT)
{
mResourcesHLSL->samplerMetadataUniforms(out, 4);
}
if (mUsesVertexID)
{
out << " uint dx_VertexID : packoffset(c3.w);\n";
}
out << "};\n"
"\n";
}
else
{
if (mUsesDepthRange)
{
out << "uniform float3 dx_DepthRange : register(c0);\n";
}
out << "uniform float4 dx_ViewAdjust : register(c1);\n";
out << "uniform float2 dx_ViewCoords : register(c2);\n"
"\n";
}
if (mUsesDepthRange)
{
out << "static gl_DepthRangeParameters gl_DepthRange = {dx_DepthRange.x, "
"dx_DepthRange.y, dx_DepthRange.z};\n"
"\n";
}
}
else // Compute shader
{
ASSERT(mShaderType == GL_COMPUTE_SHADER);
out << "cbuffer DriverConstants : register(b1)\n"
"{\n";
if (mUsesNumWorkGroups)
{
out << " uint3 gl_NumWorkGroups : packoffset(c0);\n";
}
ASSERT(mOutputType == SH_HLSL_4_1_OUTPUT);
unsigned int registerIndex = 1;
mResourcesHLSL->samplerMetadataUniforms(out, registerIndex);
// Sampler metadata struct must be two 4-vec, 32 bytes.
registerIndex += mResourcesHLSL->getSamplerCount() * 2;
mResourcesHLSL->imageMetadataUniforms(out, registerIndex);
out << "};\n";
out << kImage2DFunctionString << "\n";
std::ostringstream systemValueDeclaration = sh::InitializeStream<std::ostringstream>();
std::ostringstream glBuiltinInitialization = sh::InitializeStream<std::ostringstream>();
systemValueDeclaration << "\nstruct CS_INPUT\n{\n";
glBuiltinInitialization << "\nvoid initGLBuiltins(CS_INPUT input)\n"
<< "{\n";
if (mUsesWorkGroupID)
{
out << "static uint3 gl_WorkGroupID = uint3(0, 0, 0);\n";
systemValueDeclaration << " uint3 dx_WorkGroupID : "
<< "SV_GroupID;\n";
glBuiltinInitialization << " gl_WorkGroupID = input.dx_WorkGroupID;\n";
}
if (mUsesLocalInvocationID)
{
out << "static uint3 gl_LocalInvocationID = uint3(0, 0, 0);\n";
systemValueDeclaration << " uint3 dx_LocalInvocationID : "
<< "SV_GroupThreadID;\n";
glBuiltinInitialization << " gl_LocalInvocationID = input.dx_LocalInvocationID;\n";
}
if (mUsesGlobalInvocationID)
{
out << "static uint3 gl_GlobalInvocationID = uint3(0, 0, 0);\n";
systemValueDeclaration << " uint3 dx_GlobalInvocationID : "
<< "SV_DispatchThreadID;\n";
glBuiltinInitialization << " gl_GlobalInvocationID = input.dx_GlobalInvocationID;\n";
}
if (mUsesLocalInvocationIndex)
{
out << "static uint gl_LocalInvocationIndex = uint(0);\n";
systemValueDeclaration << " uint dx_LocalInvocationIndex : "
<< "SV_GroupIndex;\n";
glBuiltinInitialization
<< " gl_LocalInvocationIndex = input.dx_LocalInvocationIndex;\n";
}
systemValueDeclaration << "};\n\n";
glBuiltinInitialization << "};\n\n";
out << systemValueDeclaration.str();
out << glBuiltinInitialization.str();
}
if (!mappedStructs.empty())
{
out << "// Structures from std140 blocks with padding removed\n";
out << "\n";
out << mappedStructs;
out << "\n";
}
bool getDimensionsIgnoresBaseLevel =
(mCompileOptions & SH_HLSL_GET_DIMENSIONS_IGNORES_BASE_LEVEL) != 0;
mTextureFunctionHLSL->textureFunctionHeader(out, mOutputType, getDimensionsIgnoresBaseLevel);
mImageFunctionHLSL->imageFunctionHeader(out);
mAtomicCounterFunctionHLSL->atomicCounterFunctionHeader(out);
if (mUsesFragCoord)
{
out << "#define GL_USES_FRAG_COORD\n";
}
if (mUsesPointCoord)
{
out << "#define GL_USES_POINT_COORD\n";
}
if (mUsesFrontFacing)
{
out << "#define GL_USES_FRONT_FACING\n";
}
if (mUsesHelperInvocation)
{
out << "#define GL_USES_HELPER_INVOCATION\n";
}
if (mUsesPointSize)
{
out << "#define GL_USES_POINT_SIZE\n";
}
if (mHasMultiviewExtensionEnabled)
{
out << "#define GL_ANGLE_MULTIVIEW_ENABLED\n";
}
if (mUsesVertexID)
{
out << "#define GL_USES_VERTEX_ID\n";
}
if (mUsesViewID)
{
out << "#define GL_USES_VIEW_ID\n";
}
if (mUsesFragDepth)
{
out << "#define GL_USES_FRAG_DEPTH\n";
}
if (mUsesDepthRange)
{
out << "#define GL_USES_DEPTH_RANGE\n";
}
if (mUsesXor)
{
out << "bool xor(bool p, bool q)\n"
"{\n"
" return (p || q) && !(p && q);\n"
"}\n"
"\n";
}
builtInFunctionEmulator->outputEmulatedFunctions(out);
}
void OutputHLSL::visitSymbol(TIntermSymbol *node)
{
const TVariable &variable = node->variable();
// Empty symbols can only appear in declarations and function arguments, and in either of those
// cases the symbol nodes are not visited.
ASSERT(variable.symbolType() != SymbolType::Empty);
TInfoSinkBase &out = getInfoSink();
// Handle accessing std140 structs by value
if (IsInStd140UniformBlock(node) && node->getBasicType() == EbtStruct &&
needStructMapping(node))
{
mNeedStructMapping = true;
out << "map";
}
const ImmutableString &name = variable.name();
const TSymbolUniqueId &uniqueId = variable.uniqueId();
if (name == "gl_DepthRange")
{
mUsesDepthRange = true;
out << name;
}
else if (IsAtomicCounter(variable.getType().getBasicType()))
{
const TType &variableType = variable.getType();
if (variableType.getQualifier() == EvqUniform)
{
TLayoutQualifier layout = variableType.getLayoutQualifier();
mReferencedUniforms[uniqueId.get()] = &variable;
out << getAtomicCounterNameForBinding(layout.binding) << ", " << layout.offset;
}
else
{
TString varName = DecorateVariableIfNeeded(variable);
out << varName << ", " << varName << "_offset";
}
}
else
{
const TType &variableType = variable.getType();
TQualifier qualifier = variable.getType().getQualifier();
ensureStructDefined(variableType);
if (qualifier == EvqUniform)
{
const TInterfaceBlock *interfaceBlock = variableType.getInterfaceBlock();
if (interfaceBlock)
{
if (mReferencedUniformBlocks.count(interfaceBlock->uniqueId().get()) == 0)
{
const TVariable *instanceVariable = nullptr;
if (variableType.isInterfaceBlock())
{
instanceVariable = &variable;
}
mReferencedUniformBlocks[interfaceBlock->uniqueId().get()] =
new TReferencedBlock(interfaceBlock, instanceVariable);
}
}
else
{
mReferencedUniforms[uniqueId.get()] = &variable;
}
out << DecorateVariableIfNeeded(variable);
}
else if (qualifier == EvqBuffer)
{
UNREACHABLE();
}
else if (qualifier == EvqAttribute || qualifier == EvqVertexIn)
{
mReferencedAttributes[uniqueId.get()] = &variable;
out << Decorate(name);
}
else if (IsVarying(qualifier))
{
mReferencedVaryings[uniqueId.get()] = &variable;
out << DecorateVariableIfNeeded(variable);
if (variable.symbolType() == SymbolType::AngleInternal && name == "ViewID_OVR")
{
mUsesViewID = true;
}
}
else if (qualifier == EvqFragmentOut)
{
mReferencedOutputVariables[uniqueId.get()] = &variable;
out << "out_" << name;
}
else if (qualifier == EvqFragColor)
{
out << "gl_Color[0]";
mUsesFragColor = true;
}
else if (qualifier == EvqFragData)
{
out << "gl_Color";
mUsesFragData = true;
}
else if (qualifier == EvqSecondaryFragColorEXT)
{
out << "gl_SecondaryColor[0]";
mUsesSecondaryColor = true;
}
else if (qualifier == EvqSecondaryFragDataEXT)
{
out << "gl_SecondaryColor";
mUsesSecondaryColor = true;
}
else if (qualifier == EvqFragCoord)
{
mUsesFragCoord = true;
out << name;
}
else if (qualifier == EvqPointCoord)
{
mUsesPointCoord = true;
out << name;
}
else if (qualifier == EvqFrontFacing)
{
mUsesFrontFacing = true;
out << name;
}
else if (qualifier == EvqHelperInvocation)
{
mUsesHelperInvocation = true;
out << name;
}
else if (qualifier == EvqPointSize)
{
mUsesPointSize = true;
out << name;
}
else if (qualifier == EvqInstanceID)
{
mUsesInstanceID = true;
out << name;
}
else if (qualifier == EvqVertexID)
{
mUsesVertexID = true;
out << name;
}
else if (name == "gl_FragDepthEXT" || name == "gl_FragDepth")
{
mUsesFragDepth = true;
out << "gl_Depth";
}
else if (qualifier == EvqNumWorkGroups)
{
mUsesNumWorkGroups = true;
out << name;
}
else if (qualifier == EvqWorkGroupID)
{
mUsesWorkGroupID = true;
out << name;
}
else if (qualifier == EvqLocalInvocationID)
{
mUsesLocalInvocationID = true;
out << name;
}
else if (qualifier == EvqGlobalInvocationID)
{
mUsesGlobalInvocationID = true;
out << name;
}
else if (qualifier == EvqLocalInvocationIndex)
{
mUsesLocalInvocationIndex = true;
out << name;
}
else
{
out << DecorateVariableIfNeeded(variable);
}
}
}
void OutputHLSL::outputEqual(Visit visit, const TType &type, TOperator op, TInfoSinkBase &out)
{
if (type.isScalar() && !type.isArray())
{
if (op == EOpEqual)
{
outputTriplet(out, visit, "(", " == ", ")");
}
else
{
outputTriplet(out, visit, "(", " != ", ")");
}
}
else
{
if (visit == PreVisit && op == EOpNotEqual)
{
out << "!";
}
if (type.isArray())
{
const TString &functionName = addArrayEqualityFunction(type);
outputTriplet(out, visit, (functionName + "(").c_str(), ", ", ")");
}
else if (type.getBasicType() == EbtStruct)
{
const TStructure &structure = *type.getStruct();
const TString &functionName = addStructEqualityFunction(structure);
outputTriplet(out, visit, (functionName + "(").c_str(), ", ", ")");
}
else
{
ASSERT(type.isMatrix() || type.isVector());
outputTriplet(out, visit, "all(", " == ", ")");
}
}
}
void OutputHLSL::outputAssign(Visit visit, const TType &type, TInfoSinkBase &out)
{
if (type.isArray())
{
const TString &functionName = addArrayAssignmentFunction(type);
outputTriplet(out, visit, (functionName + "(").c_str(), ", ", ")");
}
else
{
outputTriplet(out, visit, "(", " = ", ")");
}
}
bool OutputHLSL::ancestorEvaluatesToSamplerInStruct()
{
for (unsigned int n = 0u; getAncestorNode(n) != nullptr; ++n)
{
TIntermNode *ancestor = getAncestorNode(n);
const TIntermBinary *ancestorBinary = ancestor->getAsBinaryNode();
if (ancestorBinary == nullptr)
{
return false;
}
switch (ancestorBinary->getOp())
{
case EOpIndexDirectStruct:
{
const TStructure *structure = ancestorBinary->getLeft()->getType().getStruct();
const TIntermConstantUnion *index =
ancestorBinary->getRight()->getAsConstantUnion();
const TField *field = structure->fields()[index->getIConst(0)];
if (IsSampler(field->type()->getBasicType()))
{
return true;
}
break;
}
case EOpIndexDirect:
break;
default:
// Returning a sampler from indirect indexing is not supported.
return false;
}
}
return false;
}
bool OutputHLSL::visitSwizzle(Visit visit, TIntermSwizzle *node)
{
TInfoSinkBase &out = getInfoSink();
if (visit == PostVisit)
{
out << ".";
node->writeOffsetsAsXYZW(&out);
}
return true;
}
bool OutputHLSL::visitBinary(Visit visit, TIntermBinary *node)
{
TInfoSinkBase &out = getInfoSink();
switch (node->getOp())
{
case EOpComma:
outputTriplet(out, visit, "(", ", ", ")");
break;
case EOpAssign:
if (node->isArray())
{
TIntermAggregate *rightAgg = node->getRight()->getAsAggregate();
if (rightAgg != nullptr && rightAgg->isConstructor())
{
const TString &functionName = addArrayConstructIntoFunction(node->getType());
out << functionName << "(";
node->getLeft()->traverse(this);
TIntermSequence *seq = rightAgg->getSequence();
for (auto &arrayElement : *seq)
{
out << ", ";
arrayElement->traverse(this);
}
out << ")";
return false;
}
// ArrayReturnValueToOutParameter should have eliminated expressions where a
// function call is assigned.
ASSERT(rightAgg == nullptr);
}
// Assignment expressions with atomic functions should be transformed into atomic
// function calls in HLSL.
// e.g. original_value = atomicAdd(dest, value) should be translated into
// InterlockedAdd(dest, value, original_value);
else if (IsAtomicFunctionForSharedVariableDirectAssign(*node))
{
TIntermAggregate *atomicFunctionNode = node->getRight()->getAsAggregate();
TOperator atomicFunctionOp = atomicFunctionNode->getOp();
out << GetHLSLAtomicFunctionStringAndLeftParenthesis(atomicFunctionOp);
TIntermSequence *argumentSeq = atomicFunctionNode->getSequence();
ASSERT(argumentSeq->size() >= 2u);
for (auto &argument : *argumentSeq)
{
argument->traverse(this);
out << ", ";
}
node->getLeft()->traverse(this);
out << ")";
return false;
}
else if (IsInShaderStorageBlock(node->getLeft()))
{
mSSBOOutputHLSL->outputStoreFunctionCallPrefix(node->getLeft());
out << ", ";
if (IsInShaderStorageBlock(node->getRight()))
{
mSSBOOutputHLSL->outputLoadFunctionCall(node->getRight());
}
else
{
node->getRight()->traverse(this);
}
out << ")";
return false;
}
else if (IsInShaderStorageBlock(node->getRight()))
{
node->getLeft()->traverse(this);
out << " = ";
mSSBOOutputHLSL->outputLoadFunctionCall(node->getRight());
return false;
}
outputAssign(visit, node->getType(), out);
break;
case EOpInitialize:
if (visit == PreVisit)
{
TIntermSymbol *symbolNode = node->getLeft()->getAsSymbolNode();
ASSERT(symbolNode);
TIntermTyped *initializer = node->getRight();
// Global initializers must be constant at this point.
ASSERT(symbolNode->getQualifier() != EvqGlobal || initializer->hasConstantValue());
// GLSL allows to write things like "float x = x;" where a new variable x is defined
// and the value of an existing variable x is assigned. HLSL uses C semantics (the
// new variable is created before the assignment is evaluated), so we need to
// convert
// this to "float t = x, x = t;".
if (writeSameSymbolInitializer(out, symbolNode, initializer))
{
// Skip initializing the rest of the expression
return false;
}
else if (writeConstantInitialization(out, symbolNode, initializer))
{
return false;
}
}
else if (visit == InVisit)
{
out << " = ";
if (IsInShaderStorageBlock(node->getRight()))
{
mSSBOOutputHLSL->outputLoadFunctionCall(node->getRight());
return false;
}
}
break;
case EOpAddAssign:
outputTriplet(out, visit, "(", " += ", ")");
break;
case EOpSubAssign:
outputTriplet(out, visit, "(", " -= ", ")");
break;
case EOpMulAssign:
outputTriplet(out, visit, "(", " *= ", ")");
break;
case EOpVectorTimesScalarAssign:
outputTriplet(out, visit, "(", " *= ", ")");
break;
case EOpMatrixTimesScalarAssign:
outputTriplet(out, visit, "(", " *= ", ")");
break;
case EOpVectorTimesMatrixAssign:
if (visit == PreVisit)
{
out << "(";
}
else if (visit == InVisit)
{
out << " = mul(";
node->getLeft()->traverse(this);
out << ", transpose(";
}
else
{
out << ")))";
}
break;
case EOpMatrixTimesMatrixAssign:
if (visit == PreVisit)
{
out << "(";
}
else if (visit == InVisit)
{
out << " = transpose(mul(transpose(";
node->getLeft()->traverse(this);
out << "), transpose(";
}
else
{
out << "))))";
}
break;
case EOpDivAssign:
outputTriplet(out, visit, "(", " /= ", ")");
break;
case EOpIModAssign:
outputTriplet(out, visit, "(", " %= ", ")");
break;
case EOpBitShiftLeftAssign:
outputTriplet(out, visit, "(", " <<= ", ")");
break;
case EOpBitShiftRightAssign:
outputTriplet(out, visit, "(", " >>= ", ")");
break;
case EOpBitwiseAndAssign:
outputTriplet(out, visit, "(", " &= ", ")");
break;
case EOpBitwiseXorAssign:
outputTriplet(out, visit, "(", " ^= ", ")");
break;
case EOpBitwiseOrAssign:
outputTriplet(out, visit, "(", " |= ", ")");
break;
case EOpIndexDirect:
{
const TType &leftType = node->getLeft()->getType();
if (leftType.isInterfaceBlock())
{
if (visit == PreVisit)
{
TIntermSymbol *instanceArraySymbol = node->getLeft()->getAsSymbolNode();
const TInterfaceBlock *interfaceBlock = leftType.getInterfaceBlock();
ASSERT(leftType.getQualifier() == EvqUniform);
if (mReferencedUniformBlocks.count(interfaceBlock->uniqueId().get()) == 0)
{
mReferencedUniformBlocks[interfaceBlock->uniqueId().get()] =
new TReferencedBlock(interfaceBlock, &instanceArraySymbol->variable());
}
const int arrayIndex = node->getRight()->getAsConstantUnion()->getIConst(0);
out << mResourcesHLSL->InterfaceBlockInstanceString(
instanceArraySymbol->getName(), arrayIndex);
return false;
}
}
else if (ancestorEvaluatesToSamplerInStruct())
{
// All parts of an expression that access a sampler in a struct need to use _ as
// separator to access the sampler variable that has been moved out of the struct.
outputTriplet(out, visit, "", "_", "");
}
else if (IsAtomicCounter(leftType.getBasicType()))
{
outputTriplet(out, visit, "", " + (", ") * ATOMIC_COUNTER_ARRAY_STRIDE");
}
else
{
outputTriplet(out, visit, "", "[", "]");
}
}
break;
case EOpIndexIndirect:
{
// We do not currently support indirect references to interface blocks
ASSERT(node->getLeft()->getBasicType() != EbtInterfaceBlock);
const TType &leftType = node->getLeft()->getType();
if (IsAtomicCounter(leftType.getBasicType()))
{
outputTriplet(out, visit, "", " + (", ") * ATOMIC_COUNTER_ARRAY_STRIDE");
}
else
{
outputTriplet(out, visit, "", "[", "]");
}
break;
}
case EOpIndexDirectStruct:
{
const TStructure *structure = node->getLeft()->getType().getStruct();
const TIntermConstantUnion *index = node->getRight()->getAsConstantUnion();
const TField *field = structure->fields()[index->getIConst(0)];
// In cases where indexing returns a sampler, we need to access the sampler variable
// that has been moved out of the struct.
bool indexingReturnsSampler = IsSampler(field->type()->getBasicType());
if (visit == PreVisit && indexingReturnsSampler)
{
// Samplers extracted from structs have "angle" prefix to avoid name conflicts.
// This prefix is only output at the beginning of the indexing expression, which
// may have multiple parts.
out << "angle";
}
if (!indexingReturnsSampler)
{
// All parts of an expression that access a sampler in a struct need to use _ as
// separator to access the sampler variable that has been moved out of the struct.
indexingReturnsSampler = ancestorEvaluatesToSamplerInStruct();
}
if (visit == InVisit)
{
if (indexingReturnsSampler)
{
out << "_" << field->name();
}
else
{
out << "." << DecorateField(field->name(), *structure);
}
return false;
}
}
break;
case EOpIndexDirectInterfaceBlock:
{
ASSERT(!IsInShaderStorageBlock(node->getLeft()));
bool structInStd140UniformBlock = node->getBasicType() == EbtStruct &&
IsInStd140UniformBlock(node->getLeft()) &&
needStructMapping(node);
if (visit == PreVisit && structInStd140UniformBlock)
{
mNeedStructMapping = true;
out << "map";
}
if (visit == InVisit)
{
const TInterfaceBlock *interfaceBlock =
node->getLeft()->getType().getInterfaceBlock();
const TIntermConstantUnion *index = node->getRight()->getAsConstantUnion();
const TField *field = interfaceBlock->fields()[index->getIConst(0)];
if (structInStd140UniformBlock)
{
out << "_";
}
else
{
out << ".";
}
out << Decorate(field->name());
return false;
}
break;
}
case EOpAdd:
outputTriplet(out, visit, "(", " + ", ")");
break;
case EOpSub:
outputTriplet(out, visit, "(", " - ", ")");
break;
case EOpMul:
outputTriplet(out, visit, "(", " * ", ")");
break;
case EOpDiv:
outputTriplet(out, visit, "(", " / ", ")");
break;
case EOpIMod:
outputTriplet(out, visit, "(", " % ", ")");
break;
case EOpBitShiftLeft:
outputTriplet(out, visit, "(", " << ", ")");
break;
case EOpBitShiftRight:
outputTriplet(out, visit, "(", " >> ", ")");
break;
case EOpBitwiseAnd:
outputTriplet(out, visit, "(", " & ", ")");
break;
case EOpBitwiseXor:
outputTriplet(out, visit, "(", " ^ ", ")");
break;
case EOpBitwiseOr:
outputTriplet(out, visit, "(", " | ", ")");
break;
case EOpEqual:
case EOpNotEqual:
outputEqual(visit, node->getLeft()->getType(), node->getOp(), out);
break;
case EOpLessThan:
outputTriplet(out, visit, "(", " < ", ")");
break;
case EOpGreaterThan:
outputTriplet(out, visit, "(", " > ", ")");
break;
case EOpLessThanEqual:
outputTriplet(out, visit, "(", " <= ", ")");
break;
case EOpGreaterThanEqual:
outputTriplet(out, visit, "(", " >= ", ")");
break;
case EOpVectorTimesScalar:
outputTriplet(out, visit, "(", " * ", ")");
break;
case EOpMatrixTimesScalar:
outputTriplet(out, visit, "(", " * ", ")");
break;
case EOpVectorTimesMatrix:
outputTriplet(out, visit, "mul(", ", transpose(", "))");
break;
case EOpMatrixTimesVector:
outputTriplet(out, visit, "mul(transpose(", "), ", ")");
break;
case EOpMatrixTimesMatrix:
outputTriplet(out, visit, "transpose(mul(transpose(", "), transpose(", ")))");
break;
case EOpLogicalOr:
// HLSL doesn't short-circuit ||, so we assume that || affected by short-circuiting have
// been unfolded.
ASSERT(!node->getRight()->hasSideEffects());
outputTriplet(out, visit, "(", " || ", ")");
return true;
case EOpLogicalXor:
mUsesXor = true;
outputTriplet(out, visit, "xor(", ", ", ")");
break;
case EOpLogicalAnd:
// HLSL doesn't short-circuit &&, so we assume that && affected by short-circuiting have
// been unfolded.
ASSERT(!node->getRight()->hasSideEffects());
outputTriplet(out, visit, "(", " && ", ")");
return true;
default:
UNREACHABLE();
}
return true;
}
bool OutputHLSL::visitUnary(Visit visit, TIntermUnary *node)
{
TInfoSinkBase &out = getInfoSink();
switch (node->getOp())
{
case EOpNegative:
outputTriplet(out, visit, "(-", "", ")");
break;
case EOpPositive:
outputTriplet(out, visit, "(+", "", ")");
break;
case EOpLogicalNot:
outputTriplet(out, visit, "(!", "", ")");
break;
case EOpBitwiseNot:
outputTriplet(out, visit, "(~", "", ")");
break;
case EOpPostIncrement:
outputTriplet(out, visit, "(", "", "++)");
break;
case EOpPostDecrement:
outputTriplet(out, visit, "(", "", "--)");
break;
case EOpPreIncrement:
outputTriplet(out, visit, "(++", "", ")");
break;
case EOpPreDecrement:
outputTriplet(out, visit, "(--", "", ")");
break;
case EOpRadians:
outputTriplet(out, visit, "radians(", "", ")");
break;
case EOpDegrees:
outputTriplet(out, visit, "degrees(", "", ")");
break;
case EOpSin:
outputTriplet(out, visit, "sin(", "", ")");
break;
case EOpCos:
outputTriplet(out, visit, "cos(", "", ")");
break;
case EOpTan:
outputTriplet(out, visit, "tan(", "", ")");
break;
case EOpAsin:
outputTriplet(out, visit, "asin(", "", ")");
break;
case EOpAcos:
outputTriplet(out, visit, "acos(", "", ")");
break;
case EOpAtan:
outputTriplet(out, visit, "atan(", "", ")");
break;
case EOpSinh:
outputTriplet(out, visit, "sinh(", "", ")");
break;
case EOpCosh:
outputTriplet(out, visit, "cosh(", "", ")");
break;
case EOpTanh:
case EOpAsinh:
case EOpAcosh:
case EOpAtanh:
ASSERT(node->getUseEmulatedFunction());
writeEmulatedFunctionTriplet(out, visit, node->getOp());
break;
case EOpExp:
outputTriplet(out, visit, "exp(", "", ")");
break;
case EOpLog:
outputTriplet(out, visit, "log(", "", ")");
break;
case EOpExp2:
outputTriplet(out, visit, "exp2(", "", ")");
break;
case EOpLog2:
outputTriplet(out, visit, "log2(", "", ")");
break;
case EOpSqrt:
outputTriplet(out, visit, "sqrt(", "", ")");
break;
case EOpInversesqrt:
outputTriplet(out, visit, "rsqrt(", "", ")");
break;
case EOpAbs:
outputTriplet(out, visit, "abs(", "", ")");
break;
case EOpSign:
outputTriplet(out, visit, "sign(", "", ")");
break;
case EOpFloor:
outputTriplet(out, visit, "floor(", "", ")");
break;
case EOpTrunc:
outputTriplet(out, visit, "trunc(", "", ")");
break;
case EOpRound:
outputTriplet(out, visit, "round(", "", ")");
break;
case EOpRoundEven:
ASSERT(node->getUseEmulatedFunction());
writeEmulatedFunctionTriplet(out, visit, node->getOp());
break;
case EOpCeil:
outputTriplet(out, visit, "ceil(", "", ")");
break;
case EOpFract:
outputTriplet(out, visit, "frac(", "", ")");
break;
case EOpIsnan:
if (node->getUseEmulatedFunction())
writeEmulatedFunctionTriplet(out, visit, node->getOp());
else
outputTriplet(out, visit, "isnan(", "", ")");
mRequiresIEEEStrictCompiling = true;
break;
case EOpIsinf:
outputTriplet(out, visit, "isinf(", "", ")");
break;
case EOpFloatBitsToInt:
outputTriplet(out, visit, "asint(", "", ")");
break;
case EOpFloatBitsToUint:
outputTriplet(out, visit, "asuint(", "", ")");
break;
case EOpIntBitsToFloat:
outputTriplet(out, visit, "asfloat(", "", ")");
break;
case EOpUintBitsToFloat:
outputTriplet(out, visit, "asfloat(", "", ")");
break;
case EOpPackSnorm2x16:
case EOpPackUnorm2x16:
case EOpPackHalf2x16:
case EOpUnpackSnorm2x16:
case EOpUnpackUnorm2x16:
case EOpUnpackHalf2x16:
case EOpPackUnorm4x8:
case EOpPackSnorm4x8:
case EOpUnpackUnorm4x8:
case EOpUnpackSnorm4x8:
ASSERT(node->getUseEmulatedFunction());
writeEmulatedFunctionTriplet(out, visit, node->getOp());
break;
case EOpLength:
outputTriplet(out, visit, "length(", "", ")");
break;
case EOpNormalize:
outputTriplet(out, visit, "normalize(", "", ")");
break;
case EOpDFdx:
if (mInsideDiscontinuousLoop || mOutputLod0Function)
{
outputTriplet(out, visit, "(", "", ", 0.0)");
}
else
{
outputTriplet(out, visit, "ddx(", "", ")");
}
break;
case EOpDFdy:
if (mInsideDiscontinuousLoop || mOutputLod0Function)
{
outputTriplet(out, visit, "(", "", ", 0.0)");
}
else
{
outputTriplet(out, visit, "ddy(", "", ")");
}
break;
case EOpFwidth:
if (mInsideDiscontinuousLoop || mOutputLod0Function)
{
outputTriplet(out, visit, "(", "", ", 0.0)");
}
else
{
outputTriplet(out, visit, "fwidth(", "", ")");
}
break;
case EOpTranspose:
outputTriplet(out, visit, "transpose(", "", ")");
break;
case EOpDeterminant:
outputTriplet(out, visit, "determinant(transpose(", "", "))");
break;
case EOpInverse:
ASSERT(node->getUseEmulatedFunction());
writeEmulatedFunctionTriplet(out, visit, node->getOp());
break;
case EOpAny:
outputTriplet(out, visit, "any(", "", ")");
break;
case EOpAll:
outputTriplet(out, visit, "all(", "", ")");
break;
case EOpLogicalNotComponentWise:
outputTriplet(out, visit, "(!", "", ")");
break;
case EOpBitfieldReverse:
outputTriplet(out, visit, "reversebits(", "", ")");
break;
case EOpBitCount:
outputTriplet(out, visit, "countbits(", "", ")");
break;
case EOpFindLSB:
// Note that it's unclear from the HLSL docs what this returns for 0, but this is tested
// in GLSLTest and results are consistent with GL.
outputTriplet(out, visit, "firstbitlow(", "", ")");
break;
case EOpFindMSB:
// Note that it's unclear from the HLSL docs what this returns for 0 or -1, but this is
// tested in GLSLTest and results are consistent with GL.
outputTriplet(out, visit, "firstbithigh(", "", ")");
break;
case EOpArrayLength:
{
TIntermTyped *operand = node->getOperand();
ASSERT(IsInShaderStorageBlock(operand));
mSSBOOutputHLSL->outputLengthFunctionCall(operand);
return false;
}
default:
UNREACHABLE();
}
return true;
}
ImmutableString OutputHLSL::samplerNamePrefixFromStruct(TIntermTyped *node)
{
if (node->getAsSymbolNode())
{
ASSERT(node->getAsSymbolNode()->variable().symbolType() != SymbolType::Empty);
return node->getAsSymbolNode()->getName();
}
TIntermBinary *nodeBinary = node->getAsBinaryNode();
switch (nodeBinary->getOp())
{
case EOpIndexDirect:
{
int index = nodeBinary->getRight()->getAsConstantUnion()->getIConst(0);
std::stringstream prefixSink = sh::InitializeStream<std::stringstream>();
prefixSink << samplerNamePrefixFromStruct(nodeBinary->getLeft()) << "_" << index;
return ImmutableString(prefixSink.str());
}
case EOpIndexDirectStruct:
{
const TStructure *s = nodeBinary->getLeft()->getAsTyped()->getType().getStruct();
int index = nodeBinary->getRight()->getAsConstantUnion()->getIConst(0);
const TField *field = s->fields()[index];
std::stringstream prefixSink = sh::InitializeStream<std::stringstream>();
prefixSink << samplerNamePrefixFromStruct(nodeBinary->getLeft()) << "_"
<< field->name();
return ImmutableString(prefixSink.str());
}
default:
UNREACHABLE();
return kEmptyImmutableString;
}
}
bool OutputHLSL::visitBlock(Visit visit, TIntermBlock *node)
{
TInfoSinkBase &out = getInfoSink();
bool isMainBlock = mInsideMain && getParentNode()->getAsFunctionDefinition();
if (mInsideFunction)
{
outputLineDirective(out, node->getLine().first_line);
out << "{\n";
if (isMainBlock)
{
if (mShaderType == GL_COMPUTE_SHADER)
{
out << "initGLBuiltins(input);\n";
}
else
{
out << "@@ MAIN PROLOGUE @@\n";
}
}
}
for (TIntermNode *statement : *node->getSequence())
{
outputLineDirective(out, statement->getLine().first_line);
statement->traverse(this);
// Don't output ; after case labels, they're terminated by :
// This is needed especially since outputting a ; after a case statement would turn empty
// case statements into non-empty case statements, disallowing fall-through from them.
// Also the output code is clearer if we don't output ; after statements where it is not
// needed:
// * if statements
// * switch statements
// * blocks
// * function definitions
// * loops (do-while loops output the semicolon in VisitLoop)
// * declarations that don't generate output.
if (statement->getAsCaseNode() == nullptr && statement->getAsIfElseNode() == nullptr &&
statement->getAsBlock() == nullptr && statement->getAsLoopNode() == nullptr &&
statement->getAsSwitchNode() == nullptr &&
statement->getAsFunctionDefinition() == nullptr &&
(statement->getAsDeclarationNode() == nullptr ||
IsDeclarationWrittenOut(statement->getAsDeclarationNode())) &&
statement->getAsGlobalQualifierDeclarationNode() == nullptr)
{
out << ";\n";
}
}
if (mInsideFunction)
{
outputLineDirective(out, node->getLine().last_line);
if (isMainBlock && shaderNeedsGenerateOutput())
{
// We could have an empty main, a main function without a branch at the end, or a main
// function with a discard statement at the end. In these cases we need to add a return
// statement.
bool needReturnStatement =
node->getSequence()->empty() || !node->getSequence()->back()->getAsBranchNode() ||
node->getSequence()->back()->getAsBranchNode()->getFlowOp() != EOpReturn;
if (needReturnStatement)
{
out << "return " << generateOutputCall() << ";\n";
}
}
out << "}\n";
}
return false;
}
bool OutputHLSL::visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node)
{
TInfoSinkBase &out = getInfoSink();
ASSERT(mCurrentFunctionMetadata == nullptr);
size_t index = mCallDag.findIndex(node->getFunction()->uniqueId());
ASSERT(index != CallDAG::InvalidIndex);
mCurrentFunctionMetadata = &mASTMetadataList[index];
const TFunction *func = node->getFunction();
if (func->isMain())
{
// The stub strings below are replaced when shader is dynamically defined by its layout:
switch (mShaderType)
{
case GL_VERTEX_SHADER:
out << "@@ VERTEX ATTRIBUTES @@\n\n"
<< "@@ VERTEX OUTPUT @@\n\n"
<< "VS_OUTPUT main(VS_INPUT input)";
break;
case GL_FRAGMENT_SHADER:
out << "@@ PIXEL OUTPUT @@\n\n"
<< "PS_OUTPUT main(@@ PIXEL MAIN PARAMETERS @@)";
break;
case GL_COMPUTE_SHADER:
out << "[numthreads(" << mWorkGroupSize[0] << ", " << mWorkGroupSize[1] << ", "
<< mWorkGroupSize[2] << ")]\n";
out << "void main(CS_INPUT input)";
break;
default:
UNREACHABLE();
break;
}
}
else
{
out << TypeString(node->getFunctionPrototype()->getType()) << " ";
out << DecorateFunctionIfNeeded(func) << DisambiguateFunctionName(func)
<< (mOutputLod0Function ? "Lod0(" : "(");
size_t paramCount = func->getParamCount();
for (unsigned int i = 0; i < paramCount; i++)
{
const TVariable *param = func->getParam(i);
ensureStructDefined(param->getType());
writeParameter(param, out);
if (i < paramCount - 1)
{
out << ", ";
}
}
out << ")\n";
}
mInsideFunction = true;
if (func->isMain())
{
mInsideMain = true;
}
// The function body node will output braces.
node->getBody()->traverse(this);
mInsideFunction = false;
mInsideMain = false;
mCurrentFunctionMetadata = nullptr;
bool needsLod0 = mASTMetadataList[index].mNeedsLod0;
if (needsLod0 && !mOutputLod0Function && mShaderType == GL_FRAGMENT_SHADER)
{
ASSERT(!node->getFunction()->isMain());
mOutputLod0Function = true;
node->traverse(this);
mOutputLod0Function = false;
}
return false;
}
bool OutputHLSL::visitDeclaration(Visit visit, TIntermDeclaration *node)
{
if (visit == PreVisit)
{
TIntermSequence *sequence = node->getSequence();
TIntermTyped *declarator = (*sequence)[0]->getAsTyped();
ASSERT(sequence->size() == 1);
ASSERT(declarator);
if (IsDeclarationWrittenOut(node))
{
TInfoSinkBase &out = getInfoSink();
ensureStructDefined(declarator->getType());
if (!declarator->getAsSymbolNode() ||
declarator->getAsSymbolNode()->variable().symbolType() !=
SymbolType::Empty) // Variable declaration
{
if (declarator->getQualifier() == EvqShared)
{
out << "groupshared ";
}
else if (!mInsideFunction)
{
out << "static ";
}
out << TypeString(declarator->getType()) + " ";
TIntermSymbol *symbol = declarator->getAsSymbolNode();
if (symbol)
{
symbol->traverse(this);
out << ArrayString(symbol->getType());
// Temporarily disable shadred memory initialization. It is very slow for D3D11
// drivers to compile a compute shader if we add code to initialize a
// groupshared array variable with a large array size. And maybe produce
// incorrect result. See http://anglebug.com/3226.
if (declarator->getQualifier() != EvqShared)
{
out << " = " + zeroInitializer(symbol->getType());
}
}
else
{
declarator->traverse(this);
}
}
}
else if (IsVaryingOut(declarator->getQualifier()))
{
TIntermSymbol *symbol = declarator->getAsSymbolNode();
ASSERT(symbol); // Varying declarations can't have initializers.
const TVariable &variable = symbol->variable();
if (variable.symbolType() != SymbolType::Empty)
{
// Vertex outputs which are declared but not written to should still be declared to
// allow successful linking.
mReferencedVaryings[symbol->uniqueId().get()] = &variable;
}
}
}
return false;
}
bool OutputHLSL::visitGlobalQualifierDeclaration(Visit visit,
TIntermGlobalQualifierDeclaration *node)
{
// Do not do any translation
return false;
}
void OutputHLSL::visitFunctionPrototype(TIntermFunctionPrototype *node)
{
TInfoSinkBase &out = getInfoSink();
size_t index = mCallDag.findIndex(node->getFunction()->uniqueId());
// Skip the prototype if it is not implemented (and thus not used)
if (index == CallDAG::InvalidIndex)
{
return;
}
const TFunction *func = node->getFunction();
TString name = DecorateFunctionIfNeeded(func);
out << TypeString(node->getType()) << " " << name << DisambiguateFunctionName(func)
<< (mOutputLod0Function ? "Lod0(" : "(");
size_t paramCount = func->getParamCount();
for (unsigned int i = 0; i < paramCount; i++)
{
writeParameter(func->getParam(i), out);
if (i < paramCount - 1)
{
out << ", ";
}
}
out << ");\n";
// Also prototype the Lod0 variant if needed
bool needsLod0 = mASTMetadataList[index].mNeedsLod0;
if (needsLod0 && !mOutputLod0Function && mShaderType == GL_FRAGMENT_SHADER)
{
mOutputLod0Function = true;
node->traverse(this);
mOutputLod0Function = false;
}
}
bool OutputHLSL::visitAggregate(Visit visit, TIntermAggregate *node)
{
TInfoSinkBase &out = getInfoSink();
switch (node->getOp())
{
case EOpCallBuiltInFunction:
case EOpCallFunctionInAST:
case EOpCallInternalRawFunction:
{
TIntermSequence *arguments = node->getSequence();
bool lod0 = (mInsideDiscontinuousLoop || mOutputLod0Function) &&
mShaderType == GL_FRAGMENT_SHADER;
if (node->getOp() == EOpCallFunctionInAST)
{
if (node->isArray())
{
UNIMPLEMENTED();
}
size_t index = mCallDag.findIndex(node->getFunction()->uniqueId());
ASSERT(index != CallDAG::InvalidIndex);
lod0 &= mASTMetadataList[index].mNeedsLod0;
out << DecorateFunctionIfNeeded(node->getFunction());
out << DisambiguateFunctionName(node->getSequence());
out << (lod0 ? "Lod0(" : "(");
}
else if (node->getOp() == EOpCallInternalRawFunction)
{
// This path is used for internal functions that don't have their definitions in the
// AST, such as precision emulation functions.
out << DecorateFunctionIfNeeded(node->getFunction()) << "(";
}
else if (node->getFunction()->isImageFunction())
{
const ImmutableString &name = node->getFunction()->name();
TType type = (*arguments)[0]->getAsTyped()->getType();
const ImmutableString &imageFunctionName = mImageFunctionHLSL->useImageFunction(
name, type.getBasicType(), type.getLayoutQualifier().imageInternalFormat,
type.getMemoryQualifier().readonly);
out << imageFunctionName << "(";
}
else if (node->getFunction()->isAtomicCounterFunction())
{
const ImmutableString &name = node->getFunction()->name();
ImmutableString atomicFunctionName =
mAtomicCounterFunctionHLSL->useAtomicCounterFunction(name);
out << atomicFunctionName << "(";
}
else
{
const ImmutableString &name = node->getFunction()->name();
TBasicType samplerType = (*arguments)[0]->getAsTyped()->getType().getBasicType();
int coords = 0; // textureSize(gsampler2DMS) doesn't have a second argument.
if (arguments->size() > 1)
{
coords = (*arguments)[1]->getAsTyped()->getNominalSize();
}
const ImmutableString &textureFunctionName =
mTextureFunctionHLSL->useTextureFunction(name, samplerType, coords,
arguments->size(), lod0, mShaderType);
out << textureFunctionName << "(";
}
for (TIntermSequence::iterator arg = arguments->begin(); arg != arguments->end(); arg++)
{
TIntermTyped *typedArg = (*arg)->getAsTyped();
if (mOutputType == SH_HLSL_4_0_FL9_3_OUTPUT && IsSampler(typedArg->getBasicType()))
{
out << "texture_";
(*arg)->traverse(this);
out << ", sampler_";
}
(*arg)->traverse(this);
if (typedArg->getType().isStructureContainingSamplers())
{
const TType &argType = typedArg->getType();
TVector<const TVariable *> samplerSymbols;
ImmutableString structName = samplerNamePrefixFromStruct(typedArg);
std::string namePrefix = "angle_";
namePrefix += structName.data();
argType.createSamplerSymbols(ImmutableString(namePrefix), "", &samplerSymbols,
nullptr, mSymbolTable);
for (const TVariable *sampler : samplerSymbols)
{
if (mOutputType == SH_HLSL_4_0_FL9_3_OUTPUT)
{
out << ", texture_" << sampler->name();
out << ", sampler_" << sampler->name();
}
else
{
// In case of HLSL 4.1+, this symbol is the sampler index, and in case
// of D3D9, it's the sampler variable.
out << ", " << sampler->name();
}
}
}
if (arg < arguments->end() - 1)
{
out << ", ";
}
}
out << ")";
return false;
}
case EOpConstruct:
outputConstructor(out, visit, node);
break;
case EOpEqualComponentWise:
outputTriplet(out, visit, "(", " == ", ")");
break;
case EOpNotEqualComponentWise:
outputTriplet(out, visit, "(", " != ", ")");
break;
case EOpLessThanComponentWise:
outputTriplet(out, visit, "(", " < ", ")");
break;
case EOpGreaterThanComponentWise:
outputTriplet(out, visit, "(", " > ", ")");
break;
case EOpLessThanEqualComponentWise:
outputTriplet(out, visit, "(", " <= ", ")");
break;
case EOpGreaterThanEqualComponentWise:
outputTriplet(out, visit, "(", " >= ", ")");
break;
case EOpMod:
ASSERT(node->getUseEmulatedFunction());
writeEmulatedFunctionTriplet(out, visit, node->getOp());
break;
case EOpModf:
outputTriplet(out, visit, "modf(", ", ", ")");
break;
case EOpPow:
outputTriplet(out, visit, "pow(", ", ", ")");
break;
case EOpAtan:
ASSERT(node->getSequence()->size() == 2); // atan(x) is a unary operator
ASSERT(node->getUseEmulatedFunction());
writeEmulatedFunctionTriplet(out, visit, node->getOp());
break;
case EOpMin:
outputTriplet(out, visit, "min(", ", ", ")");
break;
case EOpMax:
outputTriplet(out, visit, "max(", ", ", ")");
break;
case EOpClamp:
outputTriplet(out, visit, "clamp(", ", ", ")");
break;
case EOpMix:
{
TIntermTyped *lastParamNode = (*(node->getSequence()))[2]->getAsTyped();
if (lastParamNode->getType().getBasicType() == EbtBool)
{
// There is no HLSL equivalent for ESSL3 built-in "genType mix (genType x, genType
// y, genBType a)",
// so use emulated version.
ASSERT(node->getUseEmulatedFunction());
writeEmulatedFunctionTriplet(out, visit, node->getOp());
}
else
{
outputTriplet(out, visit, "lerp(", ", ", ")");
}
break;
}
case EOpStep:
outputTriplet(out, visit, "step(", ", ", ")");
break;
case EOpSmoothstep:
outputTriplet(out, visit, "smoothstep(", ", ", ")");
break;
case EOpFrexp:
case EOpLdexp:
ASSERT(node->getUseEmulatedFunction());
writeEmulatedFunctionTriplet(out, visit, node->getOp());
break;
case EOpDistance:
outputTriplet(out, visit, "distance(", ", ", ")");
break;
case EOpDot:
outputTriplet(out, visit, "dot(", ", ", ")");
break;
case EOpCross:
outputTriplet(out, visit, "cross(", ", ", ")");
break;
case EOpFaceforward:
ASSERT(node->getUseEmulatedFunction());
writeEmulatedFunctionTriplet(out, visit, node->getOp());
break;
case EOpReflect:
outputTriplet(out, visit, "reflect(", ", ", ")");
break;
case EOpRefract:
outputTriplet(out, visit, "refract(", ", ", ")");
break;
case EOpOuterProduct:
ASSERT(node->getUseEmulatedFunction());
writeEmulatedFunctionTriplet(out, visit, node->getOp());
break;
case EOpMulMatrixComponentWise:
outputTriplet(out, visit, "(", " * ", ")");
break;
case EOpBitfieldExtract:
case EOpBitfieldInsert:
case EOpUaddCarry:
case EOpUsubBorrow:
case EOpUmulExtended:
case EOpImulExtended:
ASSERT(node->getUseEmulatedFunction());
writeEmulatedFunctionTriplet(out, visit, node->getOp());
break;
case EOpBarrier:
// barrier() is translated to GroupMemoryBarrierWithGroupSync(), which is the
// cheapest *WithGroupSync() function, without any functionality loss, but
// with the potential for severe performance loss.
outputTriplet(out, visit, "GroupMemoryBarrierWithGroupSync(", "", ")");
break;
case EOpMemoryBarrierShared:
outputTriplet(out, visit, "GroupMemoryBarrier(", "", ")");
break;
case EOpMemoryBarrierAtomicCounter:
case EOpMemoryBarrierBuffer:
case EOpMemoryBarrierImage:
outputTriplet(out, visit, "DeviceMemoryBarrier(", "", ")");
break;
case EOpGroupMemoryBarrier:
case EOpMemoryBarrier:
outputTriplet(out, visit, "AllMemoryBarrier(", "", ")");
break;
// Single atomic function calls without return value.
// e.g. atomicAdd(dest, value) should be translated into InterlockedAdd(dest, value).
case EOpAtomicAdd:
case EOpAtomicMin:
case EOpAtomicMax:
case EOpAtomicAnd:
case EOpAtomicOr:
case EOpAtomicXor:
// The parameter 'original_value' of InterlockedExchange(dest, value, original_value)
// and InterlockedCompareExchange(dest, compare_value, value, original_value) is not
// optional.
// https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/interlockedexchange
// https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/interlockedcompareexchange
// So all the call of atomicExchange(dest, value) and atomicCompSwap(dest,
// compare_value, value) should all be modified into the form of "int temp; temp =
// atomicExchange(dest, value);" and "int temp; temp = atomicCompSwap(dest,
// compare_value, value);" in the intermediate tree before traversing outputHLSL.
case EOpAtomicExchange:
case EOpAtomicCompSwap:
{
ASSERT(node->getChildCount() > 1);
TIntermTyped *memNode = (*node->getSequence())[0]->getAsTyped();
if (IsInShaderStorageBlock(memNode))
{
// Atomic memory functions for SSBO.
// "_ssbo_atomicXXX_TYPE(RWByteAddressBuffer buffer, uint loc" is written to |out|.
mSSBOOutputHLSL->outputAtomicMemoryFunctionCallPrefix(memNode, node->getOp());
// Write the rest argument list to |out|.
for (size_t i = 1; i < node->getChildCount(); i++)
{
out << ", ";
TIntermTyped *argument = (*node->getSequence())[i]->getAsTyped();
if (IsInShaderStorageBlock(argument))
{
mSSBOOutputHLSL->outputLoadFunctionCall(argument);
}
else
{
argument->traverse(this);
}
}
out << ")";
return false;
}
else
{
// Atomic memory functions for shared variable.
if (node->getOp() != EOpAtomicExchange && node->getOp() != EOpAtomicCompSwap)
{
outputTriplet(out, visit,
GetHLSLAtomicFunctionStringAndLeftParenthesis(node->getOp()), ",",
")");
}
else
{
UNREACHABLE();
}
}
break;
}
default:
UNREACHABLE();
}
return true;
}
void OutputHLSL::writeIfElse(TInfoSinkBase &out, TIntermIfElse *node)
{
out << "if (";
node->getCondition()->traverse(this);
out << ")\n";
outputLineDirective(out, node->getLine().first_line);