1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include "AzslcEmitter.h"
- #include <tuple>
- #include <cmath>
- #include <filesystem>
- namespace StdFs = std::filesystem;
- // We should only include the base platform emitter
- // Every specific implementation is supplied via a factory get method
- #include "AzslcPlatformEmitter.h"
- namespace AZ
- {
- namespace
- {
- // spec: https://github.com/microsoft/DirectXShaderCompiler/blob/master/docs/SPIR-V.rst#subpass-inputs
- /// extract: "Subpasses are read through two new builtin resource types, available only in pixel shader"
- // because we have a unified file for all stages, we need to ensure the source remains buildable for other stages.
- static constexpr char StubSubpassInputTypes[] = R"(
- #if !defined(AZ_USE_SUBPASSINPUT)
- class SubpassInputStub
- {
- float4 SubpassLoad(){return (float4)0;}
- };
- class SubpassInputStubMS
- {
- float4 SubpassLoad(int sampleIndex){return (float4)0;}
- };
- #define SubpassInput SubpassInputStub
- #define SubpassInputMS SubpassInputStubMS
- #endif
- )";
- }
- }
- namespace AZ::ShaderCompiler
- {
- // to activate argument dependent lookup from template utilities in AzslcUtils, this must be in a reachable namespace
- Streamable& operator << (Streamable& out, const AttributeInfo::Argument& arg)
- {
- if (holds_alternative<string>(arg))
- {
- return out << get<string>(arg);
- }
- else if (holds_alternative<ConstNumericVal>(arg))
- {
- const auto& constVal = get<ConstNumericVal>(arg);
- if (holds_alternative<int32_t>(constVal) || holds_alternative<uint32_t>(constVal))
- {
- return out << ExtractValueAsInt64(constVal, std::numeric_limits<int64_t>::min());
- }
- else if (holds_alternative<float>(constVal))
- {
- return out << ExtractValueAsFloat(constVal, std::numeric_limits<float>::infinity());
- }
- }
- else if (holds_alternative<bool>(arg))
- {
- return out << get<bool>(arg);
- }
- return out;
- }
- Streamable& operator << (Streamable& out, const AttributeInfo& attr)
- {
- if (!attr.m_namespace.empty())
- {
- out << attr.m_namespace << "::";
- }
- out << attr.m_attribute;
- if (!attr.m_argList.empty())
- {
- out << "(" << Join(attr.m_argList, ", ") << ")";
- }
- return out;
- }
- using std::for_each;
- void CodeEmitter::Run(const Options& options)
- {
- const uint32_t numOf32bitConst = GetNumberOf32BitConstants(options, m_ir->m_rootConstantStructUID);
- const RootSigDesc rootSig = BuildSignatureDescription(options, numOf32bitConst);
- SetupScopeMigrations(options);
- SetupOptionsSpecializationId(options);
- // Emit global attributes
- for (const auto& attr : m_ir->m_symbols.GetGlobalAttributeList())
- {
- EmitAttribute(attr);
- }
- if (m_ir->m_sema.m_subpassInputSeen)
- {
- m_out << StubSubpassInputTypes << "\n";
- }
-
- EmitGetterFunctionDeclarationsForRootConstants(m_ir->m_rootConstantStructUID);
- // loop on the (mostly) user-defined order of code entities, and emit
- for (const IdentifierUID& iteratedSymbolUid : m_ir->m_symbols.GetOrderedSymbols())
- {
- const QualifiedNameView iteratedSymbolName = iteratedSymbolUid.GetName();
- const Kind iteratedSymbolKind = m_ir->GetKind(iteratedSymbolUid);
- switch (iteratedSymbolKind)
- {
- // top-level enums, structs and classes, as well as immediate-type-declaration enum/structs (`struct S{} s;`)
- case Kind::Interface:
- case Kind::Struct:
- case Kind::Class:
- case Kind::Enum:
- {
- if (IsTopLevelThroughTranslation(iteratedSymbolUid))
- {
- EmitPreprocessorLineDirective(iteratedSymbolName);
- auto* classInfo = m_ir->GetSymbolSubAs<ClassInfo>(iteratedSymbolName);
- iteratedSymbolKind == Kind::Enum ?
- EmitEnum(iteratedSymbolUid, *classInfo, options)
- : EmitStruct(*classInfo, iteratedSymbolName, options);
- }
- break;
- }
- // typedefs
- case Kind::TypeAlias:
- {
- if (IsTopLevelThroughTranslation(iteratedSymbolUid))
- {
- EmitPreprocessorLineDirective(iteratedSymbolName);
- auto* aliasInfo = m_ir->GetSymbolSubAs<TypeAliasInfo>(iteratedSymbolName);
- EmitTypeAlias(iteratedSymbolUid, *aliasInfo, options);
- }
- break;
- }
- // global variables
- case Kind::Variable:
- {
- if (IsTopLevelThroughTranslation(iteratedSymbolUid))
- {
- auto* varInfo = m_ir->GetSymbolSubAs<VarInfo>(iteratedSymbolName);
- if (varInfo->CheckHasStorageFlag(StorageFlag::Enumerator))
- { // Enumerators have already been emitted in the Kind::Enum case.
- // They act as static const, but no need to emit them here
- break;
- }
- // Option variables are emitted after the ShaderVariantFallback SRG
- if (varInfo->CheckHasStorageFlag(StorageFlag::Option))
- {
- EmitShaderVariantOptionVariableDeclaration(iteratedSymbolUid, options);
- break;
- }
- // a non-global extern is an SRG-variable. it should be emitted by EmitSRG
- bool global = IsGlobal(iteratedSymbolName);
- if (!global && !varInfo->StorageFlagIsLocalLinkage(global || varInfo->m_srgMember))
- {
- break;
- }
- EmitPreprocessorLineDirective(iteratedSymbolName);
- EmitVariableDeclaration(*varInfo, iteratedSymbolUid, options, VarDeclHasFlag(VarDeclHas::Initializer));
- m_out << ";\n";
- }
- break;
- }
- // SRG
- case Kind::ShaderResourceGroup:
- {
- EmitPreprocessorLineDirective(iteratedSymbolName);
- auto* srgSub = m_ir->GetSymbolSubAs<SRGInfo>(iteratedSymbolName);
- EmitSRG(*srgSub, iteratedSymbolUid, options, rootSig);
- break;
- }
- // function
- case Kind::Function:
- {
- EmitPreprocessorLineDirective(iteratedSymbolName);
- auto* funcSub = m_ir->GetSymbolSubAs<FunctionInfo>(iteratedSymbolName);
- const bool alreadyDeclared = AlreadyEmittedFunctionDeclaration(iteratedSymbolUid);
- assert(!funcSub->IsEmpty());
- const EmitFunctionAs form = (funcSub->HasUniqueDeclarationThroughDefinition() || alreadyDeclared) ?
- EmitFunctionAs::Definition : EmitFunctionAs::Declaration;
- EmitFunction(*funcSub, iteratedSymbolUid, form, options);
- break;
- }
- default: break;
- } // end switch on code entities kind
- } // end for all entities
- EmitRootConstants(rootSig, options);
- EmitShaderVariantOptionGetters(options);
- if (options.m_emitRootSig)
- {
- m_out << GetPlatformEmitter().GetRootSig(*this, rootSig, options, BindingPair::Set::Merged);
- }
- }
- //! azslSymbolName is going to be considered as a startup point of migration (e.g a scope)
- //! and all symbols under that scope will be migrated equivalently.
- void CodeEmitter::MigrateASTSubTree(const IdentifierUID& azslSymbol, QualifiedNameView landingScope)
- {
- m_translations.RegisterLandingScope(azslSymbol, landingScope);
- // special case for non-scoped enums because they have dependent symbols that are not children
- if (m_ir->GetKind(azslSymbol) == Kind::Enum)
- {
- auto* sub = m_ir->GetSymbolSubAs<ClassInfo>(azslSymbol.GetName());
- bool scoped = sub->Get<EnumerationInfo>()->m_isScoped;
- if (!scoped)
- {
- // migrate enumerators too
- for (auto& enumerator : sub->GetMemberFields())
- {
- m_translations.RegisterLandingScope(enumerator, landingScope);
- }
- }
- }
- }
- bool CodeEmitter::IsTopLevelThroughTranslation(const IdentifierUID& uid) const
- {
- return m_translations.GetLandingScope(uid.GetName()) == QualifiedNameView{"/"};
- }
- //! setup all scope migrations (srg content to global, local structs to global)
- void CodeEmitter::SetupScopeMigrations(const Options& options)
- {
- m_translations.SetAccessSymbolQueryFunctor([=](QualifiedNameView qnv){return m_ir->GetKindInfo(IdentifierUID{qnv});});
- m_translations.SetGetSeenatFunctor([=](QualifiedNameView qnv) -> vector<Seenat>&
- {
- auto* uidkind = m_ir->GetIdAndKindInfo(qnv);
- if (uidkind)
- {
- return uidkind->second.GetSeenats();
- }
- static vector<Seenat> s_empty;
- return s_empty;
- });
- auto globalScope = QualifiedNameView{ "/" };
-
- // Global root constant custom behavior
- for (auto&[uid, info] : m_ir->GetOrderedSymbolsOfSubType_2<VarInfo>())
- {
- bool isAlreadyGlobal = IsGlobal(uid.GetName());
- bool isNotContainedInType = !m_ir->IsNestedStructOrEnum(uid);
- if (isAlreadyGlobal && isNotContainedInType)
- {
- auto* varInfo = m_ir->GetSymbolSubAs<VarInfo>(uid.GetName());
- const auto& rootconstantClassInfo = *m_ir->GetSymbolSubAs<ClassInfo>(m_ir->m_rootConstantStructUID.m_name);
- if (options.m_rootConstantsMaxSize && rootconstantClassInfo.HasMember(uid.GetNameLeaf())
- && varInfo->CheckHasAllStorageFlags({ StorageFlag::Rootconstant }))
- {
- IdentifierUID constructRootconstantReference = *rootconstantClassInfo.FindMemberFromLeafName(uid.GetNameLeaf());
- m_translations.RegisterLandingScope(uid, m_ir->m_rootConstantStructUID.GetName());
- m_translations.AddCustomBehavior(uid.GetName(),
- BehaviorEvent::OnReference,
- [constructRootconstantReference](QualifiedNameView, UsageContext, string proposition, ssize_t)
- {
- // Construct the rootconstant member name which is declared as global variable with the use of get functions.
- // Type _g_MyRootConstVar = GetShaderRootConst_Member(); So now we just return _g_MyRootConstVar;
- return GetGlobalRootConstantVarName(constructRootconstantReference.GetName());
- });
- }
- }
- }
- // all SRGs -> erased. Migrate all their contents to global.
- for (auto& [srgUID, srgInfo] : m_ir->GetOrderedSymbolsOfSubType_2<SRGInfo>())
- {
- array<decltype(srgInfo->m_structs)*, 6> allSrgMembersUidArrays = {&srgInfo->m_structs,
- &srgInfo->m_srViews,
- &srgInfo->m_samplers,
- &srgInfo->m_CBs,
- &srgInfo->m_nonexternVariables,
- &srgInfo->m_functions};
- for (auto& array : allSrgMembersUidArrays)
- {
- for (auto& member : *array)
- {
- auto globalScope = QualifiedNameView{"/"};
- MigrateASTSubTree(member, globalScope);
- }
- }
- // variables get special treatment in case of non-emitConstantBufferBody, because SRG-constants go in a generated-struct: <SRGNAME>_SRGConstantStruct
- for (auto& member : srgInfo->m_implicitStruct.GetMemberFields())
- {
- auto globalScope = QualifiedNameView{"/"};
- auto constantsStruct = MakeSrgConstantsStructName(srgUID);
- MigrateASTSubTree(member, options.m_emitConstantBufferBody ? globalScope : QualifiedNameView{constantsStruct});
- }
- // add a special behavior for CB, because under cb-body switch, CBs are views, thus must be indexed.
- if (options.m_emitConstantBufferBody)
- {
- for_each(srgInfo->m_CBs.begin(), srgInfo->m_CBs.end(), [this](const IdentifierUID& viewUid)
- {
- auto* varInfo = m_ir->GetSymbolSubAs<VarInfo>(viewUid.GetName());
- // all buffer types references must be mutated to [0]
- assert(varInfo->GetTypeClass() == TypeClass::ConstantBuffer);
- m_translations.AddCustomBehavior(viewUid.GetName(),
- BehaviorEvent::OnReference,
- [this](QualifiedNameView, UsageContext, const string& proposition, ssize_t tokenId)
- {
- // in cb-body mode, CB are converted to views, a non-subscripted expression
- // needs to be abstracted away (by adding [0]) to feel like a constant buffer access.
- // but since we can have arrays of constant buffers in SRG, the user may have already subscripted
- // the access, in that case we can't double the subscript or we'll emit an ill-formed program.
- auto* ast = m_ir->m_tokenMap.GetNode(tokenId);
- return proposition + (IsNextToArrayAccessExpression(ast) ? "" : "[0]");
- });
- });
- }
- else // in the else-case, we have dedicated structures for SRG constants, and references need a custom behavior
- {
- for_each(srgInfo->m_implicitStruct.GetMemberFields().begin(), srgInfo->m_implicitStruct.GetMemberFields().end(), [this, srgUID=srgUID](IdentifierUID fieldUid)
- {
- // the natural suggestion from the translation system is going to be
- // to mutate MyRsc::m_f4 to MyRsc_SRGConstantStruct::MyRsc_m_f4
- // which is the "address" of the variable declaration, but not where the instance reside.
- // we need to mutate this to MyRsc_SRGConstantBuffer.MyRsc_m_f4
- m_translations.AddCustomBehavior(fieldUid.GetName(),
- BehaviorEvent::OnReference,
- [this, srgUID=srgUID](QualifiedNameView, UsageContext, string proposition, ssize_t)
- {
- string constantBufferId = UnMangle(MakeSrgConstantsCBName(srgUID));
- string translatedFieldId = UnMangle(string{ExtractLeaf(ReMangle(proposition))});
- return constantBufferId + "." + translatedFieldId;
- });
- });
- }
- }
- // structs/classes in functions/arguments/generic-parameters, may not be valid HLSL scopes to hold types -> migrate them to global
- for (auto& [uid, info] : m_ir->GetOrderedSymbolsOfSubType_2<ClassInfo>())
- {
- bool isAlreadyGlobal = IsGlobal(uid.GetName());
- bool isNotContainedInType = !m_ir->IsNestedStructOrEnum(uid);
- if (!isAlreadyGlobal && isNotContainedInType)
- {
- MigrateASTSubTree(uid, globalScope);
- }
- }
- // DXC has made into an error the definition of typedef in classes -> move them all to global
- for (auto& [typeAliasUid, typeAliasInfo] : m_ir->GetOrderedSymbolsOfSubType_2<TypeAliasInfo>())
- {
- if (!IsGlobal(typeAliasUid.GetName()))
- {
- MigrateASTSubTree(typeAliasUid, globalScope);
- }
- }
- }
- void CodeEmitter::EmitShaderVariantOptionVariableDeclaration(const IdentifierUID& symbolUid, const Options& options) const
- {
- assert(m_ir->GetKind(symbolUid) == Kind::Variable);
- assert(IsTopLevelThroughTranslation(symbolUid));
- auto* varInfo = m_ir->GetSymbolSubAs<VarInfo>(symbolUid.GetName());
- if (options.m_useSpecializationConstantsForOptions && varInfo->m_specializationId >= 0)
- {
- m_out << GetPlatformEmitter().GetSpecializationConstant(*this, symbolUid, options);
- }
- else
- {
- EmitGetShaderKeyFunctionDeclaration(symbolUid, varInfo->GetTypeRefInfo());
- m_out << ";\n\n";
- m_out << "#if defined(" + JoinAllNestedNamesWithUnderscore(symbolUid.m_name) + "_OPTION_DEF)\n";
- EmitVariableDeclaration(*varInfo, symbolUid, options, VarDeclHasFlag(VarDeclHas::OptionDefine));
- m_out << "_OPTION_DEF ;\n#else\n";
- EmitVariableDeclaration(*varInfo, symbolUid, options, VarDeclHasFlag(VarDeclHas::OptionDefine) | VarDeclHas::Initializer);
- m_out << ";\n#endif\n";
- }
- }
- void CodeEmitter::EmitShaderVariantOptionGetters(const Options& options) const
- {
- vector<pair<IdentifierUID, VarInfo*>> symbols;
- // browse all variables
- for (const auto& [uid, varInfo] : m_ir->m_symbols.GetOrderedSymbolsOfSubType_2<VarInfo>())
- {
- // For now only emit top level options
- if (!IsTopLevelThroughTranslation(uid) || !varInfo->CheckHasStorageFlag(StorageFlag::Option))
- {
- continue;
- }
- symbols.emplace_back(uid, varInfo);
- }
- if (!symbols.empty())
- {
- m_out << "// Generated code: ShaderVariantOptions fallback value getters:\n";
- auto shaderOptions = GetVariantList(options, true);
- for (uint32_t shaderOptionIndex = 0; shaderOptionIndex < symbols.size(); ++shaderOptionIndex)
- {
- const auto& [uid, varInfo] = symbols[shaderOptionIndex];
- if (options.m_useSpecializationConstantsForOptions && varInfo->m_specializationId >= 0)
- {
- continue;
- }
- const auto keySizeInBits = shaderOptions["ShaderOptions"][shaderOptionIndex]["keySize"].asUInt();
- const auto keyOffsetBits = shaderOptions["ShaderOptions"][shaderOptionIndex]["keyOffset"].asUInt();
- const auto defaultValue = shaderOptions["ShaderOptions"][shaderOptionIndex]["defaultValue"].asString();
- EmitGetShaderKeyFunction(m_shaderVariantFallbackUid, uid, keySizeInBits, keyOffsetBits, defaultValue, varInfo->GetTypeRefInfo());
- }
- }
- }
- void CodeEmitter::EmitGetFunctionsForRootConstants(const ClassInfo& rootConstInfo, string_view bufferName) const
- {
- for (const auto& memberVar : rootConstInfo.GetMemberFields())
- {
- const auto* varInfo = m_ir->GetSymbolSubAs<VarInfo>(memberVar.m_name);
- if (!varInfo->m_isPublic)
- {
- continue;
- }
- // Construct function return type and function name
- m_out << GetTranslatedName(varInfo->GetTypeRefInfo(), UsageContext::ReferenceSite) << " ";
- m_out << GetRootConstFunctionName(memberVar);
- m_out << "\n{\n";
- // The get function of root constants should return members of root ConstantBuffer object
- // Ex: return rootCB.ExampleSRG_myRootCstfloat4;
- m_out << " return " << bufferName << "." << memberVar.GetNameLeaf() << ";\n";
- m_out << "}\n\n";
- }
- }
- void CodeEmitter::EmitRootConstants(const RootSigDesc& rootSig, const Options& options) const
- {
- if (const auto* rootConstInfo = m_ir->GetSymbolSubAs<ClassInfo>(m_ir->m_rootConstantStructUID.GetName()))
- {
- if (!rootConstInfo->GetMemberFields().empty())
- {
- m_out << GetPlatformEmitter().GetRootConstantsView(*this, rootSig, options, BindingPair::Set::Merged);
- // Emit the get function definitions which returns root const member
- EmitGetFunctionsForRootConstants(*rootConstInfo,
- GetTranslatedName(RootConstantsViewName, UsageContext::ReferenceSite));
- }
- }
- }
- void CodeEmitter::EmitPreprocessorLineDirective(size_t azslLineNumber)
- {
- if (azslLineNumber == 0)
- return; // protect for this invalid case. seems to happen for "virtual" symbols (like OverloadSet)
- size_t supposedVirtualLine = m_lineFinder->GetVirtualLineNumber(azslLineNumber);
- size_t curHlslLine = m_out.GetLineCount() + 1; // "lines" is a space that is 1-based indexed.
- auto lastEmitted = Infimum(m_alreadyEmittedPreprocessorLineDirectives, curHlslLine);
- if (lastEmitted != m_alreadyEmittedPreprocessorLineDirectives.cend())
- {
- // verify if we can skip the line emission if current stream line feed is still in sync with expectations
- // image:
- // in sync out of sync
- // 1 | #line 1 1 | #line 1
- // 2 | code 2 | code
- // 3 | newSymbol 3 | code
- // 4 | #line 2
- // 5 | newSymbol
- size_t curHlslPhysicalDistance = curHlslLine - lastEmitted->first;
- size_t lastVirtualSet = lastEmitted->second;
- size_t nonAdjustedCurrentLandingLine = lastVirtualSet + curHlslPhysicalDistance - 1; // -1 because line directives specify the NEXT line
- if (nonAdjustedCurrentLandingLine == supposedVirtualLine)
- return; // no need to emit. we can skip
- }
- // get the original file as absolute path:
- const string& originalFileName = StdFs::absolute( m_lineFinder->GetVirtualFileName(azslLineNumber) ).lexically_normal().generic_string();
- // emit the line:
- m_out << "#line " << supposedVirtualLine << " \"" << originalFileName << "\"\n";
- // remember it:
- m_alreadyEmittedPreprocessorLineDirectives[curHlslLine] = supposedVirtualLine;
- }
- void CodeEmitter::EmitPreprocessorLineDirective(QualifiedNameView symbolName)
- {
- IdAndKind* idAndkindInfo = m_ir->GetIdAndKindInfo(symbolName);
- KindInfo& info = idAndkindInfo->second;
- const auto origSourceLine = info.VisitSub(GetOrigSourceLine_Visitor{});
- EmitPreprocessorLineDirective(origSourceLine);
- }
- string CodeEmitter::EmitInheritanceList(const ClassInfo& clInfo)
- {
- string hlsl = clInfo.HasAnyBases() ? " : " : "";
- vector<string> mutatedBaseNames;
- TransformCopy(clInfo.GetBases(), mutatedBaseNames,
- [&](const IdentifierUID& uid)
- {
- return GetTranslatedName(uid, UsageContext::ReferenceSite);
- });
- hlsl += Join(mutatedBaseNames, ", ");
- return hlsl;
- }
- void CodeEmitter::EmitStruct(const ClassInfo& classInfo, string_view structuredSymName, const Options& options)
- {
- auto HlslStructuredDelcTagFromKind = [](Kind k)
- {
- switch (k)
- {
- case Kind::Struct: return "struct";
- case Kind::Class: return "class";
- case Kind::Interface: return "interface";
- default: return " ";
- }
- };
- const bool hasName = (structuredSymName.length() > 0);
- const auto tabs = " ";
- if (hasName)
- {
- EmitAllAttachedAttributes(IdentifierUID { QualifiedNameView{structuredSymName} });
- m_out << HlslStructuredDelcTagFromKind(classInfo.m_kind) << " "
- << GetTranslatedName(QualifiedNameView{structuredSymName}, UsageContext::DeclarationSite)
- << EmitInheritanceList(classInfo)
- << "\n{\n"; // conclusion of "class X : ::Stuff {"
- }
- for (const IdentifierUID& memberUid : classInfo.GetOrderedMembers())
- {
- if (structuredSymName.empty() || m_translations.GetLandingScope(memberUid.GetName()) == structuredSymName)
- {
- auto& [uid, info] = *m_ir->GetIdAndKindInfo(memberUid.GetName());
- if (info.IsKindOneOf(Kind::Class, Kind::Struct, Kind::Interface))
- {
- // recurse
- EmitStruct(info.GetSubRefAs<ClassInfo>(), uid.GetName(), options);
- }
- else if (info.IsKindOneOf(Kind::TypeAlias))
- {
- EmitTypeAlias(uid, info.GetSubRefAs<TypeAliasInfo>(), options);
- }
- else if (info.IsKindOneOf(Kind::Enum))
- {
- EmitEnum(uid, info.GetSubRefAs<ClassInfo>(), options);
- }
- else if (info.IsKindOneOf(Kind::Variable))
- {
- const auto* varInfo = m_ir->GetSymbolSubAs<VarInfo>(uid.m_name);
- assert(varInfo);
- m_out << tabs;
- EmitVariableDeclaration(*varInfo, uid, options, VarDeclHasFlag(VarDeclHas::HlslSemantics));
- m_out << ";\n";
- }
- else if (info.IsKindOneOf(Kind::Function))
- {
- const auto* func = m_ir->GetSymbolSubAs<FunctionInfo>(uid.m_name);
- assert(func);
- m_out << tabs;
- EmitFunctionAs form = func->HasUniqueDeclarationThroughDefinition() ? EmitFunctionAs::Definition : EmitFunctionAs::Declaration;
- EmitFunction(*func, uid, form, options);
- }
- else
- {
- assert(false);// "Unhandled KindInfo case in struct emission must be handled properly!"
- }
- }
- }
- if (hasName)
- {
- m_out << "};\n\n";
- }
- }
- void CodeEmitter::EmitAllAttachedAttributes(const IdentifierUID& uid, Except omissionList/*= {}*/) const
- {
- if (auto attrList = m_ir->m_symbols.GetAttributeList(uid))
- {
- for_each(attrList->begin(), attrList->end(),
- [=](auto&& attrInfo)
- {
- if (!IsIn(attrInfo.m_attribute, omissionList))
- {
- EmitAttribute(attrInfo);
- }
- });
- }
- }
- void CodeEmitter::EmitGetterFunctionDeclarationsForRootConstants(const IdentifierUID& uid) const
- {
- if (const auto* rootConstInfo = m_ir->GetSymbolSubAs<ClassInfo>(uid.GetName()))
- {
- for (const auto& memberVar : rootConstInfo->GetMemberFields())
- {
- const auto* varInfo = m_ir->GetSymbolSubAs<VarInfo>(memberVar.m_name);
- if (!varInfo->m_isPublic)
- {
- continue;
- }
- m_out << GetTranslatedName(varInfo->GetTypeRefInfo(), UsageContext::ReferenceSite) << " ";
- m_out << GetRootConstFunctionName(memberVar);
- m_out << ";\n\n";
- // Emit a global variable to call the above declared function ex: static const float _gRootConstA = GetRootConst();
- // The global variable will be the reference variable to access the root constant
- m_out << "static const ";
- m_out << GetTranslatedName(varInfo->GetTypeRefInfo(), UsageContext::ReferenceSite) << " ";
- m_out << GetGlobalRootConstantVarName(memberVar.m_name) + " = ";
- m_out << GetRootConstFunctionName(memberVar);
- m_out << ";\n\n";
- }
- }
- }
- // special adapter version for AttributeInfo argument
- static string Undecorate(string_view decoration, const AttributeInfo::Argument& arg)
- {
- std::stringstream ss;
- MakeOStreamStreamable soss(ss);
- (Streamable&)soss << arg;
- return string{AZ::Undecorate(decoration, ss.str())};
- }
- void CodeEmitter::EmitAttribute(const AttributeInfo& attrInfo) const
- {
- return EmitAttribute(attrInfo, m_out);
- }
- void CodeEmitter::EmitAttribute(const AttributeInfo& attrInfo, Streamable& outstream)
- {
- if (attrInfo.m_attribute == "verbatim")
- {
- outstream << (attrInfo.m_argList.begin() == attrInfo.m_argList.end() ? "" : Unescape(Undecorate("\"", *attrInfo.m_argList.begin())));
- auto outVer = [&](const AttributeInfo::Argument& arg) { outstream << " " << Unescape(Undecorate("\"", arg)); };
- for_each(std::next(attrInfo.m_argList.begin()), attrInfo.m_argList.end(), outVer);
- outstream << '\n';
- }
- else if (attrInfo.m_attribute == "output_format")
- {
- if (!attrInfo.m_argList.empty())
- {
- if (holds_alternative<string>(attrInfo.m_argList[0]))
- {
- string poFormat{ Trim(get<string>(attrInfo.m_argList[0]), "\"") };
- outstream << "#pragma OutputFormatHint(default " << poFormat << ")\n";
- }
- else if (attrInfo.m_argList.size() > 1 &&
- holds_alternative<ConstNumericVal>(attrInfo.m_argList[0]) &&
- holds_alternative<string>(attrInfo.m_argList[1]))
- {
- const auto rtIndex = ExtractValueAsInt64(get<ConstNumericVal>(attrInfo.m_argList[0]), std::numeric_limits<int64_t>::min());
- if (rtIndex >= 0 && rtIndex <= 7)
- {
- string poFormat{ Trim(get<string>(attrInfo.m_argList[1]), "\"") };
- outstream << "#pragma OutputFormatHint(target " << rtIndex << " " << poFormat << ")\n";
- }
- }
- }
- }
- else if (attrInfo.m_attribute == "partial")
- {
- // Reserved for ShaderResourceGroup use. Do not re-emit
- outstream << "// original attribute: [[" << attrInfo << "]]\n ";
- }
- else if (attrInfo.m_attribute == "range")
- {
- // Reserved for integer type option variables. Do not re-emit
- outstream << "// original attribute: [[" << attrInfo << "]]\n ";
- }
- else if (attrInfo.m_attribute == "no_specialization")
- {
- // Reserved for avoiding specialization of a shader option. Do not re-emit
- outstream << "// original attribute: [[" << attrInfo << "]]\n ";
- }
- else
- {
- // We don't block any attributes we don't understand - pass them through
- outstream << ((attrInfo.m_category == AttributeCategory::Single) ? "[" : "[[")
- << attrInfo
- << ((attrInfo.m_category == AttributeCategory::Single) ? "]" : "]]")
- << "\n";
- }
- }
- void CodeEmitter::EmitTypeAlias(const IdentifierUID& uid, const TypeAliasInfo& aliasInfo, const Options& options) const
- {
- using SF = StorageFlag;
- m_out << "typedef " << GetTranslatedName(aliasInfo.m_canonicalType, UsageContext::ReferenceSite, options)
- << " " << GetTranslatedName(uid, UsageContext::DeclarationSite) << ";\n";
- }
- void CodeEmitter::EmitEnum(const IdentifierUID& uid, const ClassInfo& classInfo, const Options& options)
- {
- const auto& enumInfo = get<EnumerationInfo>(classInfo.m_subInfo);
- EmitAllAttachedAttributes(uid);
- m_out << "enum " << (enumInfo.m_isScoped ? "class " : "") << GetTranslatedName(uid.m_name, UsageContext::DeclarationSite) << "\n";
- m_out << "{\n";
- for (const auto& memberUid : classInfo.GetMemberFields())
- {
- // The variable can potentially have an initialization declaration as well
- const auto* varInfoPtr = m_ir->GetSymbolSubAs<VarInfo>(memberUid.m_name);
- EmitVariableDeclaration(*varInfoPtr,
- memberUid,
- options,
- VarDeclHasFlag(VarDeclHas::Initializer) | VarDeclHas::NoType | VarDeclHas::NoModifiers);
- m_out << ",\n";
- }
- m_out << "};\n\n";
- }
- bool CodeEmitter::AlreadyEmittedFunctionDeclaration(const IdentifierUID& uid) const
- {
- return m_alreadyEmittedFunctionDeclarations.find(uid) != m_alreadyEmittedFunctionDeclarations.end();
- }
- bool CodeEmitter::AlreadyEmittedFunctionDefinition(const IdentifierUID& uid) const
- {
- return m_alreadyEmittedFunctionDefinitions.find(uid) != m_alreadyEmittedFunctionDefinitions.end();
- }
- void CodeEmitter::EmitFunction(const FunctionInfo& funcSub, const IdentifierUID& uid, EmitFunctionAs entityConfiguration, const Options& options)
- {
- // reproduce the signature `[attr] [modifiers] rettype [classnameFQN::] Name(params) [semantics]`
- bool emitAsDeclaration = entityConfiguration == EmitFunctionAs::Declaration;
- bool emitAsDefinition = entityConfiguration == EmitFunctionAs::Definition;
- bool riskDoubleEmission = emitAsDeclaration && AlreadyEmittedFunctionDeclaration(uid)
- || emitAsDefinition && AlreadyEmittedFunctionDefinition(uid);
- bool undefinedFunction = funcSub.IsUndefinedFunction();
- if (riskDoubleEmission
- || undefinedFunction && emitAsDefinition)
- {
- return;
- }
- AstFuncSig* node = funcSub.m_defNode ? funcSub.m_defNode : funcSub.m_declNode;
- EmitAllAttachedAttributes(uid);
- // emit some modifiers only in case of first declaration/definition
- // because: class C{ static vd A(); };
- // static vd C::A(){} // ill-formed HLSL. we can't repeat static here.
- Modifiers forbidden;
- bool firstDecl = !AlreadyEmittedFunctionDeclaration(uid);
- if (!firstDecl)
- {
- forbidden = Modifiers{StorageFlag::Static} | StorageFlag::Inline | StorageFlag::Extern;
- }
- // emit return type:
- m_out << GetTranslatedName(funcSub.m_returnType, UsageContext::ReferenceSite, options, forbidden) << " ";
- // emit Name
- if (entityConfiguration == EmitFunctionAs::Definition && funcSub.HasDeportedDefinition())
- {
- // emit fully qualified function name (with classname prefix).
- // surrounded by round braces because otherwise clang's greedy parsing will wreak havoc.
- // indeed `RetType ::Class::Method() {}` will be parsed as `RetType::Class::Method(){}` one id-expr.
- // since we can't use trailing return type in HLSL, the fix is to use round braces.
- // solution from https://stackoverflow.com/a/3185232/893406
- m_out << "(" << GetTranslatedName(uid, UsageContext::ReferenceSite) << ")";
- }
- else
- {
- // emit leaf function name (in AZSL all declaration sites are Identifier and not idExpressions)
- m_out << GetTranslatedName(uid, UsageContext::DeclarationSite);
- }
- // emit parameters:
- m_out << "(";
- bool inhibitInitializers = AlreadyEmittedFunctionDeclaration(uid) && !emitAsDeclaration;
- EmitParameters(funcSub.GetParameters(emitAsDeclaration).begin(), funcSub.GetParameters(emitAsDeclaration).end(), options, !inhibitInitializers);
- m_out << ")";
- if (const auto hlslSemantic = node->hlslSemantic())
- {
- m_out << " " << hlslSemantic->getText();
- }
- if (entityConfiguration == EmitFunctionAs::Declaration)
- {
- m_out << ";\n";
- m_alreadyEmittedFunctionDeclarations.insert(uid);
- }
- else
- {
- auto scopeInterval = m_ir->m_scope.m_scopeIntervals[uid];
- auto astNode = m_ir->m_tokenMap.GetNode(scopeInterval.a);
- auto funcDefNode = ExtractSpecificParent<azslParser::HlslFunctionDefinitionContext>(astNode);
- auto blockInterval = funcDefNode->block()->getSourceInterval();
- m_out << "\n";
- EmitTranspiledTokens(blockInterval);
- m_out << "\n\n";
- m_alreadyEmittedFunctionDefinitions.insert(uid);
- }
- }
- void CodeEmitter::EmitVariableDeclaration(const VarInfo& varInfo, const IdentifierUID& uid, const Options& options, VarDeclHasFlag declOptions) const
- {
- // from MSDN: https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/dx-graphics-hlsl-variable-syntax
- // [Storage_Class] [Type_Modifier] Type Name[Index] [: Semantic] [: Packoffset] [: Register]; [Annotations] [= Initial_Value]
- // example of valid HLSL statement:
- // static const int dim = 2;
- // extern uniform bool stuff[dim][1] : Color0 : register(b0) <int blabla=27; string blacksheep="Hello There";> = {{false, true}};
- const ICodeEmissionMutator* codeMutator = m_codeMutator;
- const CodeMutation* codeMutation = nullptr;
- if (codeMutator && varInfo.m_declNode)
- {
- const auto tokenIndex = varInfo.m_declNode->start->getTokenIndex();
- codeMutation = codeMutator->GetMutation(tokenIndex);
- }
- if (codeMutation && codeMutation->m_prepend)
- {
- m_out << codeMutation->m_prepend.value();
- }
- if (codeMutation && codeMutation->m_replace)
- {
- m_out << codeMutation->m_replace.value();
- }
- else
- {
- EmitAllAttachedAttributes(uid);
- // parameter in/out modifiers
- if (declOptions & VarDeclHas::InOutModifiers)
- {
- m_out << GetInputModifier(varInfo.m_typeInfoExt.m_qualifiers) << " ";
- }
- // type
- if (!(declOptions & VarDeclHas::NoType) || (declOptions & VarDeclHas::OptionDefine))
- {
- auto bannedModifiers = (declOptions & VarDeclHas::NoModifiers) ? ~Modifiers{(StorageFlag::EnumType)0} : Modifiers{};
- m_out << GetTranslatedName(varInfo.m_typeInfoExt, UsageContext::ReferenceSite, options, bannedModifiers) + " ";
- }
- // var name
- m_out << GetTranslatedName(uid.m_name, UsageContext::DeclarationSite);
- // array dimensions
- if (varInfo.m_declNode && !varInfo.m_declNode->ArrayRankSpecifiers.empty())
- {
- for (auto* rankCtx : varInfo.m_declNode->ArrayRankSpecifiers)
- {
- // the brackets are included by the rule arrayRankSpecifier
- EmitTranspiledTokens(rankCtx->getSourceInterval());
- }
- }
- else if (!varInfo.GetArrayDimensions().Empty())
- {
- m_out << varInfo.GetArrayDimensions().ToString();
- }
- // semantics
- if (varInfo.m_declNode && (declOptions & VarDeclHas::HlslSemantics))
- {
- if (auto* semanticOption = varInfo.m_declNode->SemanticOpt)
- {
- m_out << " " + semanticOption->getText();
- }
- }
- // init clause
- if (declOptions & VarDeclHas::OptionDefine)
- {
- if (declOptions & VarDeclHas::Initializer)
- {
- m_out << " = " << GetShaderKeyFunctionName(uid);
- }
- else
- {
- m_out << " = " << JoinAllNestedNamesWithUnderscore(uid.m_name);
- }
- }
- else if (declOptions & VarDeclHas::Initializer)
- {
- auto* initClause = varInfo.m_declNode ? varInfo.m_declNode->variableInitializer() : nullptr;
- if (initClause)
- {
- m_out << " ";
- EmitTranspiledTokens(initClause->getSourceInterval());
- }
- else // fallback on a potentially folded value, that has chances to work for constants like enumerators
- {
- int64_t constVal;
- if (TryGetConstExprValueAsInt64(varInfo.m_constVal, constVal))
- {
- m_out << " = " << constVal;
- }
- }
- }
- }
- if (codeMutation && codeMutation->m_append)
- {
- m_out << codeMutation->m_append.value();
- }
- }
- void CodeEmitter::EmitSRGCBUnified(const SRGInfo& srgInfo, IdentifierUID srgId, const Options& options, const RootSigDesc& rootSig)
- {
- auto bindSet = BindingPair::Set::Merged;
- // Use the uId of the SRG instead of a CBV, because we create a dummy placeholder CBV to hold the rest of the declarations:
- if (!options.m_emitConstantBufferBody)
- {
- if (srgInfo.m_implicitStruct.GetMemberFields().size() > 0)
- {
- const auto& bindInfo = rootSig.Get(srgId);
- const QualifiedName implicitStruct = MakeSrgConstantsStructName(srgId);
- const QualifiedName implicitCB = MakeSrgConstantsCBName(srgId);
- EmitStruct(srgInfo.m_implicitStruct, implicitStruct, options);
- const auto spaceX = ", space" + std::to_string(bindInfo.m_registerBinding.m_pair[bindSet].m_logicalSpace);
- const auto implicitStructForEmission = GetTranslatedName(implicitStruct, UsageContext::ReferenceSite);
- const auto implicitCBForEmission = GetTranslatedName(implicitCB, UsageContext::DeclarationSite);
- m_out << "ConstantBuffer<" << implicitStructForEmission << "> " << implicitCBForEmission << " : register(b" << bindInfo.m_registerBinding.m_pair[bindSet].m_registerIndex << spaceX << ");\n\n";
- }
- for (auto cId : srgInfo.m_CBs)
- {
- if (!m_ir->GetSymbolSubAs<VarInfo>(cId.GetName())->StorageFlagIsLocalLinkage(true))
- {
- EmitSRGCB(cId, options, rootSig);
- }
- }
- return;
- }
- if (srgInfo.m_CBs.empty() && srgInfo.m_implicitStruct.GetMemberFields().empty())
- {
- return;
- }
- const auto& bindInfo = rootSig.Get(srgId);
- m_out << "ConstantBuffer " << srgInfo.m_declNode->Name->getText() << "_CBContainer : register(b" << bindInfo.m_registerBinding.m_pair[bindSet].m_registerIndex << ")\n{\n";
- for (const auto& cId : srgInfo.m_CBs)
- {
- if (!m_ir->GetSymbolSubAs<VarInfo>(cId.GetName())->StorageFlagIsLocalLinkage(true))
- {
- const auto& uqName = cId.GetNameLeaf();
- m_out << " CBVArrayView " << uqName << ";\n";
- }
- }
- if (!srgInfo.m_implicitStruct.GetMemberFields().empty())
- {
- m_out << "\n";
- EmitStruct(srgInfo.m_implicitStruct, "", options);
- }
- m_out << "\n};\n";
- for (const auto& cId : srgInfo.m_CBs)
- {
- if (!m_ir->GetSymbolSubAs<VarInfo>(cId.GetName())->StorageFlagIsLocalLinkage(true))
- {
- const auto* memberInfo = m_ir->GetSymbolSubAs<VarInfo>(cId.m_name);
- const auto& cbName = ReplaceSeparators(cId.m_name, Underscore);
- const auto& uqName = cId.GetNameLeaf();
- if (memberInfo->IsConstantBuffer())
- {
- const auto& templatedCB = "RegularBuffer<" + GetTranslatedName(memberInfo->GetGenericParameterTypeId(), UsageContext::ReferenceSite) + "> ";
- m_out << "static const " << templatedCB << cbName << " = " << templatedCB << "(" << uqName << ");\n";
- }
- }
- }
- m_out << "\n\n";
- }
- void CodeEmitter::EmitSRGCB(const IdentifierUID& cId, const Options& options, const RootSigDesc& rootSig) const
- {
- EmitAllAttachedAttributes(cId);
- auto bindSet = BindingPair::Set::Merged;
- assert(!options.m_emitConstantBufferBody);
- const auto& bindInfo = rootSig.Get(cId);
- const auto* varInfo = m_ir->GetSymbolSubAs<VarInfo>(cId.m_name);
- auto cbName = ReplaceSeparators(cId.m_name, Underscore);
- assert(varInfo->IsConstantBuffer());
- // note: instead of redoing this work ad-hoc, EmitText could be used directly on the ext type.
- const auto genericType = "<" + GetTranslatedName(varInfo->m_typeInfoExt.m_genericParameter, UsageContext::ReferenceSite) + ">";
- const string spaceX = ", space" + std::to_string(bindInfo.m_registerBinding.m_pair[bindSet].m_logicalSpace);
- m_out << "ConstantBuffer " << genericType << " " << cbName;
- if (bindInfo.m_isUnboundedArray)
- {
- m_out << "[]";
- }
- else if (bindInfo.m_registerRange > 1)
- {
- m_out << "[" << bindInfo.m_registerRange << "]";
- }
- m_out << " : register(b" << bindInfo.m_registerBinding.m_pair[bindSet].m_registerIndex << spaceX << ");\n\n";
- }
- void CodeEmitter::EmitSRGSampler(const IdentifierUID& sId, const Options& options, const RootSigDesc& rootSig) const
- {
- EmitAllAttachedAttributes(sId);
- auto bindSet = BindingPair::Set::Merged;
- const auto& bindInfo = rootSig.Get(sId);
- const auto* varInfo = m_ir->GetSymbolSubAs<VarInfo>(sId.m_name);
- const string spaceX = ", space" + std::to_string(bindInfo.m_registerBinding.m_pair[bindSet].m_logicalSpace);
- m_out << (varInfo->m_samplerState->m_isComparison ? "SamplerComparisonState " : "SamplerState ")
- << ReplaceSeparators(sId.m_name, Underscore);
- if (bindInfo.m_isUnboundedArray)
- {
- m_out << "[]";
- }
- else if (bindInfo.m_registerRange > 1)
- {
- m_out << "[" << bindInfo.m_registerRange << "]";
- }
- m_out << " : register(s" << bindInfo.m_registerBinding.m_pair[bindSet].m_registerIndex << spaceX << ")";
- m_out << ";\n\n";
- }
- //! For scope-migration-aware name emission of symbol names
- string CodeEmitter::GetTranslatedName(QualifiedNameView mangledName, UsageContext context, ssize_t tokenId /*= NotOverToken*/) const
- {
- return m_translations.GetTranslatedName(mangledName, context, tokenId);
- }
- string CodeEmitter::GetTranslatedName(const IdentifierUID& uid, UsageContext context, ssize_t tokenId /*= NotOverToken*/) const
- {
- return GetTranslatedName(uid.m_name, context, tokenId);
- }
- string CodeEmitter::GetTranslatedName(const TypeRefInfo& typeRef, UsageContext context, ssize_t tokenId /*= NotOverToken*/) const
- {
- return GetTranslatedName(typeRef.m_typeId, context, tokenId);
- }
- string CodeEmitter::GetTranslatedName(const ExtendedTypeInfo& extTypeInfo, UsageContext context, const Options& options, Modifiers forbidden /*= {}*/, ssize_t tokenId /*= NotOverToken*/) const
- {
- return GetExtendedTypeInfo(extTypeInfo, options, forbidden,
- [this, context, tokenId](const TypeRefInfo& tri){ return GetTranslatedName(tri, context, tokenId); });
- }
- void CodeEmitter::EmitSRGDataView(const IdentifierUID& tId, const Options& options, const RootSigDesc& rootSig) const
- {
- EmitAllAttachedAttributes(tId, Except{ { "input_attachment_index" } });
- auto bindSet = BindingPair::Set::Merged;
- auto& bindInfo = rootSig.Get(tId);
- auto* varInfo = m_ir->GetSymbolSubAs<VarInfo>(tId.m_name);
- string varType = GetTranslatedName(varInfo->m_typeInfoExt, UsageContext::DeclarationSite, options);
- auto registerTypeLetter = ToLower(BindingType::ToStr(RootParamTypeToBindingType(bindInfo.m_type)));
- optional<string> stringifiedLogicalSpace = std::to_string(bindInfo.m_registerBinding.m_pair[bindSet].m_logicalSpace);
- // depending on platforms we may have supplementary attributes or/and type modifier.
- auto [prefix, suffix] = GetPlatformEmitter().GetDataViewHeaderFooter(*this, tId, bindInfo.m_registerBinding.m_pair[bindSet].m_registerIndex, registerTypeLetter, stringifiedLogicalSpace);
- m_out << prefix;
- // declaration of the view variable on the global scope.
- // type unmangled_path_to_symbol [optional_array_dimension] : optional_register_binding_as_suffix
- m_out << varType << " " << ReplaceSeparators(tId.m_name, Underscore);
- if (bindInfo.m_isUnboundedArray)
- {
- m_out << "[]";
- }
- else if (bindInfo.m_registerRange > 1)
- {
- m_out << "[" << bindInfo.m_registerRange << "]";
- }
- m_out << suffix;
- auto interval = m_ir->m_scope.m_scopeIntervals[tId];
- EmitTranspiledTokens(interval);
- m_out << ";\n\n";
- }
- void CodeEmitter::EmitGetShaderKeyFunctionDeclaration(const IdentifierUID& getterUid, const TypeRefInfo& returnType) const
- {
- m_out << GetTranslatedName(returnType, UsageContext::ReferenceSite) << " ";
- m_out << GetShaderKeyFunctionName(getterUid);
- }
- void CodeEmitter::EmitGetShaderKeyFunction(const IdentifierUID& shaderKeyUid, const IdentifierUID& getterUid, uint32_t keySizeInBits, uint32_t keyOffsetBits, string_view defaultValue, const TypeRefInfo& returnType) const
- {
- // Because we use uint on the shader source side no shader option can cross the 32-bit boundary
- // This is already ensured by the emission side, in Json::Value CodeEmitter::GetVariantList(...)
- uint32_t arraySlot = keyOffsetBits / AZ::ShaderCompiler::kShaderVariantKeyRegisterSize;
- keyOffsetBits -= arraySlot * AZ::ShaderCompiler::kShaderVariantKeyRegisterSize;
- uint32_t swizzle = keyOffsetBits / AZ::ShaderCompiler::kShaderVariantKeyElementSize;
- keyOffsetBits -= swizzle * AZ::ShaderCompiler::kShaderVariantKeyElementSize;
- // Intentional unnamed scope for error-checking
- {
- auto& varInfo = *m_ir->GetSymbolSubAs<VarInfo>(shaderKeyUid.m_name);
- auto dims = varInfo.m_typeInfoExt.GetDimensions();
- assert(dims.m_dimensions.size() == 1); // This is generated variable, it must have exactly 1 array dimension
- if (arraySlot >= dims.m_dimensions[0])
- {
- const string errorMessage = ConcatString("The option {", UnmangleTrimedName(getterUid.m_name), "} exceeds the number of bits (",
- AZ::ShaderCompiler::kShaderVariantKeyRegisterSize * dims.m_dimensions[0], ") allowed by the ShaderVariantFallback.\n",
- "Either increase the limit or remove some options!");
- throw AzslcEmitterException(EMITTER_OPTION_EXCEEDING_BITS_COUNT, errorMessage);
- }
- }
- // The most significant bits are put in the first element, then the next, etc.
- // The bit order within an element is swapped so the most significant option
- // is at 0x00000001 and the least significant (32th bit) is at 0x80000000.
- // This is tailored to the runtime to reduce CB compile times.
- const char suffix[] = { 'x', 'y', 'z', 'w' };
- m_out << "\n";
- const auto mask = static_cast<uint32_t>(pow(2, keySizeInBits) - 1);
- EmitGetShaderKeyFunctionDeclaration(getterUid, returnType);
- m_out << "\n{\n";
- if (keySizeInBits > 0) // Emit the option getter function body using CB access and bit decoding.
- {
- m_out << " uint shaderKey = ((" << GetTranslatedName(shaderKeyUid, UsageContext::ReferenceSite)
- << "[" << arraySlot << "]." << suffix[swizzle] << " >> " << keyOffsetBits << ") & " << mask << ")";
- // Also we need to reproduce the "range minimal" value as an addition post big-field extraction.
- // because the CB compact storage into bits of an uint4 does not incorporate the offset of the range
- // because they are "useless" (compressable) entropy bits that would take space in the key.
- if (returnType.m_typeId.m_name.find("int") != string::npos) // if type int/uint/int16_t... then it's an integer range.
- {
- if (auto attrInfo = m_ir->m_symbols.GetAttribute(getterUid, "range"))
- {
- // Presence of the correct arglist has already been verified in Backend::AppendOptionRange
- m_out << " + " << ExtractValueAsInt64(get<ConstNumericVal>(attrInfo->m_argList[0]));
- }
- }
- else if (returnType.m_typeClass == TypeClass::Enum)
- {
- // In the case of an enumeration,
- // recovering the correct value will depend if there are jumps in the enumerators.
- // To discover that, we will use the presence of initializers.
- // one initializer on the first enumerator is a special case because it still allows
- // us to reconstruct the value from an integer cast and a +first enumerator.
- // In a case of disparate enumerators, we'll need to reconstruct the values
- // by emitting a switch case.
- // We don't want that switch case as a generic case, that could simplify azslc's source
- // but it risks slowing down the shader runtime. Sort of a pay-what-you-use principle.
- auto& enumClassInfo = *m_ir->GetSymbolSubAs<ClassInfo>(returnType.m_typeId.GetName());
- auto& enumerators = enumClassInfo.GetOrderedMembers();
- bool otherInitializers = std::any_of(enumerators.begin() + 1, enumerators.end(),
- [&](const IdentifierUID& e)
- {
- auto& var = *m_ir->GetSymbolSubAs<VarInfo>(e.GetName());
- return !!var.m_declNodeEnum->Value;
- });
- if (otherInitializers)
- {
- m_out << ";\n switch (shaderKey)\n {\n";
- for (int i = 0; i < enumerators.size(); ++i)
- {
- m_out << " case " << i << ": shaderKey = (" << GetTranslatedName(returnType, UsageContext::ReferenceSite) << ")"
- << GetTranslatedName(enumerators[i], UsageContext::ReferenceSite) << "; break;\n";
- }
- m_out << " }";
- }
- else
- {
- // add the value of the first enumerator (by emitting its name)
- m_out << " + (uint)" << GetTranslatedName(enumerators.front(), UsageContext::ReferenceSite); // we can access front with no defense because keySize would be 0 if there are no enumerators.
- }
- }
- m_out << ";\n";
- m_out << " return (" << GetTranslatedName(returnType, UsageContext::ReferenceSite) << ") shaderKey;\n";
- }
- else
- {
- // In the case of an empty enumeration type (no enumerators) or empty range, the keySize will be 0
- // and this if-branch will be taken. if the programmer specified no initializer clause,
- // this would produce unbuildable HLSL and error in generatored code. We really don't want that
- // as it would be hard to diagnose for users. As a reasonable behavior, we can emit val = 0.
- string typeAsStr = GetTranslatedName(returnType, UsageContext::ReferenceSite);
- // "<type> val = (cast expr to <type>) 0 or default;"
- m_out << " " << typeAsStr << " val = (" << typeAsStr << ")" << (defaultValue.empty() ? "0" : defaultValue) << ";\n";
- m_out << " return val;\n";
- }
- m_out << "}\n\n";
- }
- void CodeEmitter::EmitSRG(const SRGInfo& srgInfo, const IdentifierUID& srgId, const Options& options, const RootSigDesc& rootSig)
- {
- RootSigDesc::SrgDesc srgDesc;
- srgDesc.m_uid = srgId;
- m_out << "/* Generated code from ";
- // We don't emit the SRG attributes (only as a comment), but they can be accessed by the srgId if needed
- EmitAllAttachedAttributes(srgId);
- m_out << " ShaderResourceGroup " << srgInfo.m_declNode->Name->getText() << "*/\n";
- for (const auto& t : srgInfo.m_srViews)
- {
- // (*1) non-extern symbols are not visible resources, and are emitted by canonical EmitVariableDeclaration path
- if (!m_ir->GetSymbolSubAs<VarInfo>(t.GetName())->StorageFlagIsLocalLinkage(true))
- {
- EmitSRGDataView(t, options, rootSig);
- }
- }
- for (const auto& s : srgInfo.m_samplers)
- {
- // same as (*1)
- if (!m_ir->GetSymbolSubAs<VarInfo>(s.GetName())->StorageFlagIsLocalLinkage(true))
- {
- EmitSRGSampler(s, options, rootSig);
- }
- }
- EmitSRGCBUnified(srgInfo, srgId, options, rootSig);
- if (srgInfo.m_shaderVariantFallback)
- {
- m_shaderVariantFallbackUid = *srgInfo.m_shaderVariantFallback;
- }
- }
- void CodeEmitter::IfIsSrgMemberValidateIsDefined(antlr4::Token* token, TokenToAst::AstNode* nodeFromToken) const
- {
- // A common user mistake is to reference an undefined SRG field like
- // "MySrg::m_someVariable", where "m_someVariable" is undefined inside "MySrg",
- // or because it's a typo. Either way let's catch the scenario and provide a meaningful message.
- if (auto nestedNameSpecifierCtx = As<azslParser::NestedNameSpecifierContext*>(nodeFromToken))
- {
- // Get the parent rule, which should be a QualifiedIdContext.
- auto qualifiedIdCtx = As<azslParser::QualifiedIdContext*>(nestedNameSpecifierCtx->parent);
- if (!qualifiedIdCtx)
- {
- const string errorMessage = FormatString("Unexpected expression '%s'", token->getText().c_str());
- throw AzslcEmitterException(EMITTER_UNEXPECTED_EXPRESSION, token, errorMessage);
- }
- // We only care to call out the error if the token has the name of an SRG.
- QualifiedName qualifiedSymbolName(FormatString("/%s", token->getText().c_str()));
- if (const IdAndKind* idAndKind = m_ir->GetIdAndKindInfo(qualifiedSymbolName))
- {
- const auto& [uid, kindInfo] = *idAndKind;
- if (kindInfo.IsKindOneOf(Kind::ShaderResourceGroup))
- {
- const string errorMessage = FormatString("Undefined ShaderResourceGroup member '%s'", qualifiedIdCtx->getText().c_str());
- throw AzslcEmitterException(EMITTER_UNDEFINED_SRG_MEMBER, qualifiedIdCtx->getStart(), errorMessage);
- }
- }
- }
- }
- // override of the base method, to incorporate symbol and expression mutations
- void CodeEmitter::EmitTranspiledTokens(misc::Interval interval, Streamable& output) const
- {
- const ICodeEmissionMutator* codeMutator = m_codeMutator;
- ssize_t ii = interval.a;
- while (ii <= interval.b)
- {
- auto* token = GetNextToken(ii /*inout*/);
-
- const auto tokenIndex = token->getTokenIndex();
- const CodeMutation* codeMutation = codeMutator ? codeMutator->GetMutation(tokenIndex) : nullptr;
- if (codeMutation && codeMutation->m_prepend)
- {
- output << codeMutation->m_prepend.value();
- }
- if (codeMutation && codeMutation->m_replace)
- {
- output << codeMutation->m_replace.value();
- }
- else
- {
- // Access the AST from the token. Note that any processing relying on this, amounts to a hacky shortcut.
- // (taken to make the economy of having to produce a specific emitters, for each AST-node/code-construct in HLSL)
- auto* astNode = m_ir->m_tokenMap.GetNode(tokenIndex);
- // watch for potential mutations in the middle of the original source.
- auto [originalSymbol, endToken] = m_translations.OverOriginalDefinitionOf(token->getTokenIndex());
- bool emitAsIs = originalSymbol.empty();
- if (!emitAsIs)
- {
- auto* withinVarDecl = ExtractSpecificParent<azslParser::VariableDeclarationStatementContext>(astNode);
- // if we are over a variable declaration, then it's a more evolved statement than just a struct/class declaration
- // in such a case, the introduction of the type (declaration) is immediately used in a larger statement,
- // so we need to re-emit it as a reference.
- if (withinVarDecl && token == withinVarDecl->variableDeclaration()->type()->start)
- {
- output << GetTranslatedName(originalSymbol, UsageContext::ReferenceSite, ii) << " ";
- }
- // that and anything else: pure UDT declarations, or typeof/typealias may be jumped over completely
- ii = endToken + 1;
- }
- else
- {
- auto idExpr = m_translations.GetIdExpression(token);
- if (!idExpr.IsEmpty())
- {
- auto getToken = [this](ssize_t& tokenId) -> string
- {
- assert(tokenId >= 0);
- auto* token = m_tokens->get(static_cast<size_t>(tokenId));
- return token->getChannel() == Token::DEFAULT_CHANNEL ? token->getText() : string{};
- };
- output << m_translations.TranslateIdExpression(idExpr, ii, getToken) << " ";
- ii += idExpr.m_span.length() - 1;
- emitAsIs = false;
- }
- }
- if (emitAsIs)
- {
- IfIsSrgMemberValidateIsDefined(token, astNode);
-
- // do minimal reformatting to have a pseudo-readable emitted code
- auto str = token->getText();
- bool lineFeed = str == ";" || str == "{";
- output << str << (lineFeed ? '\n' : ' ');
- }
- }
- if (codeMutation && codeMutation->m_append)
- {
- output << codeMutation->m_append.value();
- }
- }
- }
- }
|