| // | |
| // 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); | |
| bool discard = false; | |
| if (node->getTrueBlock()) | |
| { | |
| // The trueBlock child node will output braces. | |
| node->getTrueBlock()->traverse(this); | |
| // Detect true discard | |
| discard = (discard || FindDiscard::search(node->getTrueBlock())); | |
| } | |
| else | |
| { | |
| // TODO(oetuaho): Check if the semicolon inside is necessary. | |
| // It's there as a result of conservative refactoring of the output. | |
| out << "{;}\n"; | |
| } | |
| outputLineDirective(out, node->getLine().first_line); | |
| if (node->getFalseBlock()) | |
| { | |
| out << "else\n"; | |
| outputLineDirective(out, node->getFalseBlock()->getLine().first_line); | |
| // The falseBlock child node will output braces. | |
| node->getFalseBlock()->traverse(this); | |
| outputLineDirective(out, node->getFalseBlock()->getLine().first_line); | |
| // Detect false discard | |
| discard = (discard || FindDiscard::search(node->getFalseBlock())); | |
| } | |
| // ANGLE issue 486: Detect problematic conditional discard | |
| if (discard) | |
| { | |
| mUsesDiscardRewriting = true; | |
| } | |
| } | |
| bool OutputHLSL::visitTernary(Visit, TIntermTernary *) | |
| { | |
| // Ternary ops should have been already converted to something else in the AST. HLSL ternary | |
| // operator doesn't short-circuit, so it's not the same as the GLSL ternary operator. | |
| UNREACHABLE(); | |
| return false; | |
| } | |
| bool OutputHLSL::visitIfElse(Visit visit, TIntermIfElse *node) | |
| { | |
| TInfoSinkBase &out = getInfoSink(); | |
| ASSERT(mInsideFunction); | |
| // D3D errors when there is a gradient operation in a loop in an unflattened if. | |
| if (mShaderType == GL_FRAGMENT_SHADER && mCurrentFunctionMetadata->hasGradientLoop(node)) | |
| { | |
| out << "FLATTEN "; | |
| } | |
| writeIfElse(out, node); | |
| return false; | |
| } | |
| bool OutputHLSL::visitSwitch(Visit visit, TIntermSwitch *node) | |
| { | |
| TInfoSinkBase &out = getInfoSink(); | |
| ASSERT(node->getStatementList()); | |
| if (visit == PreVisit) | |
| { | |
| node->setStatementList(RemoveSwitchFallThrough(node->getStatementList(), mPerfDiagnostics)); | |
| } | |
| outputTriplet(out, visit, "switch (", ") ", ""); | |
| // The curly braces get written when visiting the statementList block. | |
| return true; | |
| } | |
| bool OutputHLSL::visitCase(Visit visit, TIntermCase *node) | |
| { | |
| TInfoSinkBase &out = getInfoSink(); | |
| if (node->hasCondition()) | |
| { | |
| outputTriplet(out, visit, "case (", "", "):\n"); | |
| return true; | |
| } | |
| else | |
| { | |
| out << "default:\n"; | |
| return false; | |
| } | |
| } | |
| void OutputHLSL::visitConstantUnion(TIntermConstantUnion *node) | |
| { | |
| TInfoSinkBase &out = getInfoSink(); | |
| writeConstantUnion(out, node->getType(), node->getConstantValue()); | |
| } | |
| bool OutputHLSL::visitLoop(Visit visit, TIntermLoop *node) | |
| { | |
| mNestedLoopDepth++; | |
| bool wasDiscontinuous = mInsideDiscontinuousLoop; | |
| mInsideDiscontinuousLoop = | |
| mInsideDiscontinuousLoop || mCurrentFunctionMetadata->mDiscontinuousLoops.count(node) > 0; | |
| TInfoSinkBase &out = getInfoSink(); | |
| if (mOutputType == SH_HLSL_3_0_OUTPUT) | |
| { | |
| if (handleExcessiveLoop(out, node)) | |
| { | |
| mInsideDiscontinuousLoop = wasDiscontinuous; | |
| mNestedLoopDepth--; | |
| return false; | |
| } | |
| } | |
| const char *unroll = mCurrentFunctionMetadata->hasGradientInCallGraph(node) ? "LOOP" : ""; | |
| if (node->getType() == ELoopDoWhile) | |
| { | |
| out << "{" << unroll << " do\n"; | |
| outputLineDirective(out, node->getLine().first_line); | |
| } | |
| else | |
| { | |
| out << "{" << unroll << " for("; | |
| if (node->getInit()) | |
| { | |
| node->getInit()->traverse(this); | |
| } | |
| out << "; "; | |
| if (node->getCondition()) | |
| { | |
| node->getCondition()->traverse(this); | |
| } | |
| out << "; "; | |
| if (node->getExpression()) | |
| { | |
| node->getExpression()->traverse(this); | |
| } | |
| out << ")\n"; | |
| outputLineDirective(out, node->getLine().first_line); | |
| } | |
| if (node->getBody()) | |
| { | |
| // The loop body node will output braces. | |
| node->getBody()->traverse(this); | |
| } | |
| else | |
| { | |
| // TODO(oetuaho): Check if the semicolon inside is necessary. | |
| // It's there as a result of conservative refactoring of the output. | |
| out << "{;}\n"; | |
| } | |
| outputLineDirective(out, node->getLine().first_line); | |
| if (node->getType() == ELoopDoWhile) | |
| { | |
| outputLineDirective(out, node->getCondition()->getLine().first_line); | |
| out << "while ("; | |
| node->getCondition()->traverse(this); | |
| out << ");\n"; | |
| } | |
| out << "}\n"; | |
| mInsideDiscontinuousLoop = wasDiscontinuous; | |
| mNestedLoopDepth--; | |
| return false; | |
| } | |
| bool OutputHLSL::visitBranch(Visit visit, TIntermBranch *node) | |
| { | |
| if (visit == PreVisit) | |
| { | |
| TInfoSinkBase &out = getInfoSink(); | |
| switch (node->getFlowOp()) | |
| { | |
| case EOpKill: | |
| out << "discard"; | |
| break; | |
| case EOpBreak: | |
| if (mNestedLoopDepth > 1) | |
| { | |
| mUsesNestedBreak = true; | |
| } | |
| if (mExcessiveLoopIndex) | |
| { | |
| out << "{Break"; | |
| mExcessiveLoopIndex->traverse(this); | |
| out << " = true; break;}\n"; | |
| } | |
| else | |
| { | |
| out << "break"; | |
| } | |
| break; | |
| case EOpContinue: | |
| out << "continue"; | |
| break; | |
| case EOpReturn: | |
| if (node->getExpression()) | |
| { | |
| ASSERT(!mInsideMain); | |
| out << "return "; | |
| } | |
| else | |
| { | |
| if (mInsideMain && shaderNeedsGenerateOutput()) | |
| { | |
| out << "return " << generateOutputCall(); | |
| } | |
| else | |
| { | |
| out << "return"; | |
| } | |
| } | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } | |
| return true; | |
| } | |
| // Handle loops with more than 254 iterations (unsupported by D3D9) by splitting them | |
| // (The D3D documentation says 255 iterations, but the compiler complains at anything more than | |
| // 254). | |
| bool OutputHLSL::handleExcessiveLoop(TInfoSinkBase &out, TIntermLoop *node) | |
| { | |
| const int MAX_LOOP_ITERATIONS = 254; | |
| // Parse loops of the form: | |
| // for(int index = initial; index [comparator] limit; index += increment) | |
| TIntermSymbol *index = nullptr; | |
| TOperator comparator = EOpNull; | |
| int initial = 0; | |
| int limit = 0; | |
| int increment = 0; | |
| // Parse index name and intial value | |
| if (node->getInit()) | |
| { | |
| TIntermDeclaration *init = node->getInit()->getAsDeclarationNode(); | |
| if (init) | |
| { | |
| TIntermSequence *sequence = init->getSequence(); | |
| TIntermTyped *variable = (*sequence)[0]->getAsTyped(); | |
| if (variable && variable->getQualifier() == EvqTemporary) | |
| { | |
| TIntermBinary *assign = variable->getAsBinaryNode(); | |
| if (assign->getOp() == EOpInitialize) | |
| { | |
| TIntermSymbol *symbol = assign->getLeft()->getAsSymbolNode(); | |
| TIntermConstantUnion *constant = assign->getRight()->getAsConstantUnion(); | |
| if (symbol && constant) | |
| { | |
| if (constant->getBasicType() == EbtInt && constant->isScalar()) | |
| { | |
| index = symbol; | |
| initial = constant->getIConst(0); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Parse comparator and limit value | |
| if (index != nullptr && node->getCondition()) | |
| { | |
| TIntermBinary *test = node->getCondition()->getAsBinaryNode(); | |
| if (test && test->getLeft()->getAsSymbolNode()->uniqueId() == index->uniqueId()) | |
| { | |
| TIntermConstantUnion *constant = test->getRight()->getAsConstantUnion(); | |
| if (constant) | |
| { | |
| if (constant->getBasicType() == EbtInt && constant->isScalar()) | |
| { | |
| comparator = test->getOp(); | |
| limit = constant->getIConst(0); | |
| } | |
| } | |
| } | |
| } | |
| // Parse increment | |
| if (index != nullptr && comparator != EOpNull && node->getExpression()) | |
| { | |
| TIntermBinary *binaryTerminal = node->getExpression()->getAsBinaryNode(); | |
| TIntermUnary *unaryTerminal = node->getExpression()->getAsUnaryNode(); | |
| if (binaryTerminal) | |
| { | |
| TOperator op = binaryTerminal->getOp(); | |
| TIntermConstantUnion *constant = binaryTerminal->getRight()->getAsConstantUnion(); | |
| if (constant) | |
| { | |
| if (constant->getBasicType() == EbtInt && constant->isScalar()) | |
| { | |
| int value = constant->getIConst(0); | |
| switch (op) | |
| { | |
| case EOpAddAssign: | |
| increment = value; | |
| break; | |
| case EOpSubAssign: | |
| increment = -value; | |
| break; | |
| default: | |
| UNIMPLEMENTED(); | |
| } | |
| } | |
| } | |
| } | |
| else if (unaryTerminal) | |
| { | |
| TOperator op = unaryTerminal->getOp(); | |
| switch (op) | |
| { | |
| case EOpPostIncrement: | |
| increment = 1; | |
| break; | |
| case EOpPostDecrement: | |
| increment = -1; | |
| break; | |
| case EOpPreIncrement: | |
| increment = 1; | |
| break; | |
| case EOpPreDecrement: | |
| increment = -1; | |
| break; | |
| default: | |
| UNIMPLEMENTED(); | |
| } | |
| } | |
| } | |
| if (index != nullptr && comparator != EOpNull && increment != 0) | |
| { | |
| if (comparator == EOpLessThanEqual) | |
| { | |
| comparator = EOpLessThan; | |
| limit += 1; | |
| } | |
| if (comparator == EOpLessThan) | |
| { | |
| int iterations = (limit - initial) / increment; | |
| if (iterations <= MAX_LOOP_ITERATIONS) | |
| { | |
| return false; // Not an excessive loop | |
| } | |
| TIntermSymbol *restoreIndex = mExcessiveLoopIndex; | |
| mExcessiveLoopIndex = index; | |
| out << "{int "; | |
| index->traverse(this); | |
| out << ";\n" | |
| "bool Break"; | |
| index->traverse(this); | |
| out << " = false;\n"; | |
| bool firstLoopFragment = true; | |
| while (iterations > 0) | |
| { | |
| int clampedLimit = initial + increment * std::min(MAX_LOOP_ITERATIONS, iterations); | |
| if (!firstLoopFragment) | |
| { | |
| out << "if (!Break"; | |
| index->traverse(this); | |
| out << ") {\n"; | |
| } | |
| if (iterations <= MAX_LOOP_ITERATIONS) // Last loop fragment | |
| { | |
| mExcessiveLoopIndex = nullptr; // Stops setting the Break flag | |
| } | |
| // for(int index = initial; index < clampedLimit; index += increment) | |
| const char *unroll = | |
| mCurrentFunctionMetadata->hasGradientInCallGraph(node) ? "LOOP" : ""; | |
| out << unroll << " for("; | |
| index->traverse(this); | |
| out << " = "; | |
| out << initial; | |
| out << "; "; | |
| index->traverse(this); | |
| out << " < "; | |
| out << clampedLimit; | |
| out << "; "; | |
| index->traverse(this); | |
| out << " += "; | |
| out << increment; | |
| out << ")\n"; | |
| outputLineDirective(out, node->getLine().first_line); | |
| out << "{\n"; | |
| if (node->getBody()) | |
| { | |
| node->getBody()->traverse(this); | |
| } | |
| outputLineDirective(out, node->getLine().first_line); | |
| out << ";}\n"; | |
| if (!firstLoopFragment) | |
| { | |
| out << "}\n"; | |
| } | |
| firstLoopFragment = false; | |
| initial += MAX_LOOP_ITERATIONS * increment; | |
| iterations -= MAX_LOOP_ITERATIONS; | |
| } | |
| out << "}"; | |
| mExcessiveLoopIndex = restoreIndex; | |
| return true; | |
| } | |
| else | |
| UNIMPLEMENTED(); | |
| } | |
| return false; // Not handled as an excessive loop | |
| } | |
| void OutputHLSL::outputTriplet(TInfoSinkBase &out, | |
| Visit visit, | |
| const char *preString, | |
| const char *inString, | |
| const char *postString) | |
| { | |
| if (visit == PreVisit) | |
| { | |
| out << preString; | |
| } | |
| else if (visit == InVisit) | |
| { | |
| out << inString; | |
| } | |
| else if (visit == PostVisit) | |
| { | |
| out << postString; | |
| } | |
| } | |
| void OutputHLSL::outputLineDirective(TInfoSinkBase &out, int line) | |
| { | |
| if ((mCompileOptions & SH_LINE_DIRECTIVES) && (line > 0)) | |
| { | |
| out << "\n"; | |
| out << "#line " << line; | |
| if (mSourcePath) | |
| { | |
| out << " \"" << mSourcePath << "\""; | |
| } | |
| out << "\n"; | |
| } | |
| } | |
| void OutputHLSL::writeParameter(const TVariable *param, TInfoSinkBase &out) | |
| { | |
| const TType &type = param->getType(); | |
| TQualifier qualifier = type.getQualifier(); | |
| TString nameStr = DecorateVariableIfNeeded(*param); | |
| ASSERT(nameStr != ""); // HLSL demands named arguments, also for prototypes | |
| if (IsSampler(type.getBasicType())) | |
| { | |
| if (mOutputType == SH_HLSL_4_1_OUTPUT) | |
| { | |
| // Samplers are passed as indices to the sampler array. | |
| ASSERT(qualifier != EvqOut && qualifier != EvqInOut); | |
| out << "const uint " << nameStr << ArrayString(type); | |
| return; | |
| } | |
| if (mOutputType == SH_HLSL_4_0_FL9_3_OUTPUT) | |
| { | |
| out << QualifierString(qualifier) << " " << TextureString(type.getBasicType()) | |
| << " texture_" << nameStr << ArrayString(type) << ", " << QualifierString(qualifier) | |
| << " " << SamplerString(type.getBasicType()) << " sampler_" << nameStr | |
| << ArrayString(type); | |
| return; | |
| } | |
| } | |
| // If the parameter is an atomic counter, we need to add an extra parameter to keep track of the | |
| // buffer offset. | |
| if (IsAtomicCounter(type.getBasicType())) | |
| { | |
| out << QualifierString(qualifier) << " " << TypeString(type) << " " << nameStr << ", int " | |
| << nameStr << "_offset"; | |
| } | |
| else | |
| { | |
| out << QualifierString(qualifier) << " " << TypeString(type) << " " << nameStr | |
| << ArrayString(type); | |
| } | |
| // If the structure parameter contains samplers, they need to be passed into the function as | |
| // separate parameters. HLSL doesn't natively support samplers in structs. | |
| if (type.isStructureContainingSamplers()) | |
| { | |
| ASSERT(qualifier != EvqOut && qualifier != EvqInOut); | |
| TVector<const TVariable *> samplerSymbols; | |
| std::string namePrefix = "angle"; | |
| namePrefix += nameStr.c_str(); | |
| type.createSamplerSymbols(ImmutableString(namePrefix), "", &samplerSymbols, nullptr, | |
| mSymbolTable); | |
| for (const TVariable *sampler : samplerSymbols) | |
| { | |
| const TType &samplerType = sampler->getType(); | |
| if (mOutputType == SH_HLSL_4_1_OUTPUT) | |
| { | |
| out << ", const uint " << sampler->name() << ArrayString(samplerType); | |
| } | |
| else if (mOutputType == SH_HLSL_4_0_FL9_3_OUTPUT) | |
| { | |
| ASSERT(IsSampler(samplerType.getBasicType())); | |
| out << ", " << QualifierString(qualifier) << " " | |
| << TextureString(samplerType.getBasicType()) << " texture_" << sampler->name() | |
| << ArrayString(samplerType) << ", " << QualifierString(qualifier) << " " | |
| << SamplerString(samplerType.getBasicType()) << " sampler_" << sampler->name() | |
| << ArrayString(samplerType); | |
| } | |
| else | |
| { | |
| ASSERT(IsSampler(samplerType.getBasicType())); | |
| out << ", " << QualifierString(qualifier) << " " << TypeString(samplerType) << " " | |
| << sampler->name() << ArrayString(samplerType); | |
| } | |
| } | |
| } | |
| } | |
| TString OutputHLSL::zeroInitializer(const TType &type) const | |
| { | |
| TString string; | |
| size_t size = type.getObjectSize(); | |
| if (size >= kZeroCount) | |
| { | |
| mUseZeroArray = true; | |
| } | |
| string = GetZeroInitializer(size).c_str(); | |
| return "{" + string + "}"; | |
| } | |
| void OutputHLSL::outputConstructor(TInfoSinkBase &out, Visit visit, TIntermAggregate *node) | |
| { | |
| // Array constructors should have been already pruned from the code. | |
| ASSERT(!node->getType().isArray()); | |
| if (visit == PreVisit) | |
| { | |
| TString constructorName; | |
| if (node->getBasicType() == EbtStruct) | |
| { | |
| constructorName = mStructureHLSL->addStructConstructor(*node->getType().getStruct()); | |
| } | |
| else | |
| { | |
| constructorName = | |
| mStructureHLSL->addBuiltInConstructor(node->getType(), node->getSequence()); | |
| } | |
| out << constructorName << "("; | |
| } | |
| else if (visit == InVisit) | |
| { | |
| out << ", "; | |
| } | |
| else if (visit == PostVisit) | |
| { | |
| out << ")"; | |
| } | |
| } | |
| const TConstantUnion *OutputHLSL::writeConstantUnion(TInfoSinkBase &out, | |
| const TType &type, | |
| const TConstantUnion *const constUnion) | |
| { | |
| ASSERT(!type.isArray()); | |
| const TConstantUnion *constUnionIterated = constUnion; | |
| const TStructure *structure = type.getStruct(); | |
| if (structure) | |
| { | |
| out << mStructureHLSL->addStructConstructor(*structure) << "("; | |
| const TFieldList &fields = structure->fields(); | |
| for (size_t i = 0; i < fields.size(); i++) | |
| { | |
| const TType *fieldType = fields[i]->type(); | |
| constUnionIterated = writeConstantUnion(out, *fieldType, constUnionIterated); | |
| if (i != fields.size() - 1) | |
| { | |
| out << ", "; | |
| } | |
| } | |
| out << ")"; | |
| } | |
| else | |
| { | |
| size_t size = type.getObjectSize(); | |
| bool writeType = size > 1; | |
| if (writeType) | |
| { | |
| out << TypeString(type) << "("; | |
| } | |
| constUnionIterated = writeConstantUnionArray(out, constUnionIterated, size); | |
| if (writeType) | |
| { | |
| out << ")"; | |
| } | |
| } | |
| return constUnionIterated; | |
| } | |
| void OutputHLSL::writeEmulatedFunctionTriplet(TInfoSinkBase &out, Visit visit, TOperator op) | |
| { | |
| if (visit == PreVisit) | |
| { | |
| const char *opStr = GetOperatorString(op); | |
| BuiltInFunctionEmulator::WriteEmulatedFunctionName(out, opStr); | |
| out << "("; | |
| } | |
| else | |
| { | |
| outputTriplet(out, visit, nullptr, ", ", ")"); | |
| } | |
| } | |
| bool OutputHLSL::writeSameSymbolInitializer(TInfoSinkBase &out, | |
| TIntermSymbol *symbolNode, | |
| TIntermTyped *expression) | |
| { | |
| ASSERT(symbolNode->variable().symbolType() != SymbolType::Empty); | |
| const TIntermSymbol *symbolInInitializer = FindSymbolNode(expression, symbolNode->getName()); | |
| if (symbolInInitializer) | |
| { | |
| // Type already printed | |
| out << "t" + str(mUniqueIndex) + " = "; | |
| expression->traverse(this); | |
| out << ", "; | |
| symbolNode->traverse(this); | |
| out << " = t" + str(mUniqueIndex); | |
| mUniqueIndex++; | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool OutputHLSL::writeConstantInitialization(TInfoSinkBase &out, | |
| TIntermSymbol *symbolNode, | |
| TIntermTyped *initializer) | |
| { | |
| if (initializer->hasConstantValue()) | |
| { | |
| symbolNode->traverse(this); | |
| out << ArrayString(symbolNode->getType()); | |
| out << " = {"; | |
| writeConstantUnionArray(out, initializer->getConstantValue(), | |
| initializer->getType().getObjectSize()); | |
| out << "}"; | |
| return true; | |
| } | |
| return false; | |
| } | |
| TString OutputHLSL::addStructEqualityFunction(const TStructure &structure) | |
| { | |
| const TFieldList &fields = structure.fields(); | |
| for (const auto &eqFunction : mStructEqualityFunctions) | |
| { | |
| if (eqFunction->structure == &structure) | |
| { | |
| return eqFunction->functionName; | |
| } | |
| } | |
| const TString &structNameString = StructNameString(structure); | |
| StructEqualityFunction *function = new StructEqualityFunction(); | |
| function->structure = &structure; | |
| function->functionName = "angle_eq_" + structNameString; | |
| TInfoSinkBase fnOut; | |
| fnOut << "bool " << function->functionName << "(" << structNameString << " a, " | |
| << structNameString + " b)\n" | |
| << "{\n" | |
| " return "; | |
| for (size_t i = 0; i < fields.size(); i++) | |
| { | |
| const TField *field = fields[i]; | |
| const TType *fieldType = field->type(); | |
| const TString &fieldNameA = "a." + Decorate(field->name()); | |
| const TString &fieldNameB = "b." + Decorate(field->name()); | |
| if (i > 0) | |
| { | |
| fnOut << " && "; | |
| } | |
| fnOut << "("; | |
| outputEqual(PreVisit, *fieldType, EOpEqual, fnOut); | |
| fnOut << fieldNameA; | |
| outputEqual(InVisit, *fieldType, EOpEqual, fnOut); | |
| fnOut << fieldNameB; | |
| outputEqual(PostVisit, *fieldType, EOpEqual, fnOut); | |
| fnOut << ")"; | |
| } | |
| fnOut << ";\n" | |
| << "}\n"; | |
| function->functionDefinition = fnOut.c_str(); | |
| mStructEqualityFunctions.push_back(function); | |
| mEqualityFunctions.push_back(function); | |
| return function->functionName; | |
| } | |
| TString OutputHLSL::addArrayEqualityFunction(const TType &type) | |
| { | |
| for (const auto &eqFunction : mArrayEqualityFunctions) | |
| { | |
| if (eqFunction->type == type) | |
| { | |
| return eqFunction->functionName; | |
| } | |
| } | |
| TType elementType(type); | |
| elementType.toArrayElementType(); | |
| ArrayHelperFunction *function = new ArrayHelperFunction(); | |
| function->type = type; | |
| function->functionName = ArrayHelperFunctionName("angle_eq", type); | |
| TInfoSinkBase fnOut; | |
| const TString &typeName = TypeString(type); | |
| fnOut << "bool " << function->functionName << "(" << typeName << " a" << ArrayString(type) | |
| << ", " << typeName << " b" << ArrayString(type) << ")\n" | |
| << "{\n" | |
| " for (int i = 0; i < " | |
| << type.getOutermostArraySize() | |
| << "; ++i)\n" | |
| " {\n" | |
| " if ("; | |
| outputEqual(PreVisit, elementType, EOpNotEqual, fnOut); | |
| fnOut << "a[i]"; | |
| outputEqual(InVisit, elementType, EOpNotEqual, fnOut); | |
| fnOut << "b[i]"; | |
| outputEqual(PostVisit, elementType, EOpNotEqual, fnOut); | |
| fnOut << ") { return false; }\n" | |
| " }\n" | |
| " return true;\n" | |
| "}\n"; | |
| function->functionDefinition = fnOut.c_str(); | |
| mArrayEqualityFunctions.push_back(function); | |
| mEqualityFunctions.push_back(function); | |
| return function->functionName; | |
| } | |
| TString OutputHLSL::addArrayAssignmentFunction(const TType &type) | |
| { | |
| for (const auto &assignFunction : mArrayAssignmentFunctions) | |
| { | |
| if (assignFunction.type == type) | |
| { | |
| return assignFunction.functionName; | |
| } | |
| } | |
| TType elementType(type); | |
| elementType.toArrayElementType(); | |
| ArrayHelperFunction function; | |
| function.type = type; | |
| function.functionName = ArrayHelperFunctionName("angle_assign", type); | |
| TInfoSinkBase fnOut; | |
| const TString &typeName = TypeString(type); | |
| fnOut << "void " << function.functionName << "(out " << typeName << " a" << ArrayString(type) | |
| << ", " << typeName << " b" << ArrayString(type) << ")\n" | |
| << "{\n" | |
| " for (int i = 0; i < " | |
| << type.getOutermostArraySize() | |
| << "; ++i)\n" | |
| " {\n" | |
| " "; | |
| outputAssign(PreVisit, elementType, fnOut); | |
| fnOut << "a[i]"; | |
| outputAssign(InVisit, elementType, fnOut); | |
| fnOut << "b[i]"; | |
| outputAssign(PostVisit, elementType, fnOut); | |
| fnOut << ";\n" | |
| " }\n" | |
| "}\n"; | |
| function.functionDefinition = fnOut.c_str(); | |
| mArrayAssignmentFunctions.push_back(function); | |
| return function.functionName; | |
| } | |
| TString OutputHLSL::addArrayConstructIntoFunction(const TType &type) | |
| { | |
| for (const auto &constructIntoFunction : mArrayConstructIntoFunctions) | |
| { | |
| if (constructIntoFunction.type == type) | |
| { | |
| return constructIntoFunction.functionName; | |
| } | |
| } | |
| TType elementType(type); | |
| elementType.toArrayElementType(); | |
| ArrayHelperFunction function; | |
| function.type = type; | |
| function.functionName = ArrayHelperFunctionName("angle_construct_into", type); | |
| TInfoSinkBase fnOut; | |
| const TString &typeName = TypeString(type); | |
| fnOut << "void " << function.functionName << "(out " << typeName << " a" << ArrayString(type); | |
| for (unsigned int i = 0u; i < type.getOutermostArraySize(); ++i) | |
| { | |
| fnOut << ", " << typeName << " b" << i << ArrayString(elementType); | |
| } | |
| fnOut << ")\n" | |
| "{\n"; | |
| for (unsigned int i = 0u; i < type.getOutermostArraySize(); ++i) | |
| { | |
| fnOut << " "; | |
| outputAssign(PreVisit, elementType, fnOut); | |
| fnOut << "a[" << i << "]"; | |
| outputAssign(InVisit, elementType, fnOut); | |
| fnOut << "b" << i; | |
| outputAssign(PostVisit, elementType, fnOut); | |
| fnOut << ";\n"; | |
| } | |
| fnOut << "}\n"; | |
| function.functionDefinition = fnOut.c_str(); | |
| mArrayConstructIntoFunctions.push_back(function); | |
| return function.functionName; | |
| } | |
| void OutputHLSL::ensureStructDefined(const TType &type) | |
| { | |
| const TStructure *structure = type.getStruct(); | |
| if (structure) | |
| { | |
| ASSERT(type.getBasicType() == EbtStruct); | |
| mStructureHLSL->ensureStructDefined(*structure); | |
| } | |
| } | |
| bool OutputHLSL::shaderNeedsGenerateOutput() const | |
| { | |
| return mShaderType == GL_VERTEX_SHADER || mShaderType == GL_FRAGMENT_SHADER; | |
| } | |
| const char *OutputHLSL::generateOutputCall() const | |
| { | |
| if (mShaderType == GL_VERTEX_SHADER) | |
| { | |
| return "generateOutput(input)"; | |
| } | |
| else | |
| { | |
| return "generateOutput()"; | |
| } | |
| } | |
| } // namespace sh |