12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325 |
- /*
- * 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
- *
- */
- #pragma once
- #include "AzslcMangling.h"
- #include "AzslcCommon.h"
- #include "AzslcException.h"
- #include "DiagnosticStream.h"
- #include "antlr4-runtime.h"
- #include "generated/azslLexer.h"
- #include "generated/azslParserBaseListener.h"
- #include "generated/azslParser.h"
- #include "AzslcPredefinedTypes.h"
- #include "ReflectableEnums.h"
- #include "ReflectableEnumsUtils.h"
- #if defined(_WIN32) || defined(_WIN64)
- # define AzslcStrnicmp _strnicmp
- #elif defined(__APPLE__)
- # define AzslcStrnicmp strncasecmp
- #elif defined (__linux__) || defined(__unix__)
- # include <strings.h>
- # define AzslcStrnicmp strncasecmp
- #endif
- namespace AZ::ShaderCompiler
- {
- extern DiagnosticStream verboseCout;
- extern DiagnosticStream warningCout;
- extern Endl azEndl;
- using AstType = azslParser::TypeContext; // all usertypes and predefined, but cannot be Void
- using AstTypeofNode = azslParser::TypeofExpressionContext;
- using AstPredefinedTypeNode = azslParser::PredefinedTypeContext;
- using AstClassDeclNode = azslParser::ClassDefinitionContext;
- using AstStructDeclNode = azslParser::StructDefinitionContext;
- using AstEnumDeclNode = azslParser::EnumDefinitionContext;
- using AstInterfaceDeclNode = azslParser::InterfaceDefinitionContext;
- using AstSRGSemanticDeclNode = azslParser::SrgSemanticContext;
- using AstSRGSemanticMemberDeclNode = azslParser::SrgSemanticMemberDeclarationContext;
- using AstSRGDeclNode = azslParser::SrgDefinitionContext;
- using AstSRGMemberNode = azslParser::SrgMemberDeclarationContext;
- using AstNamedVarDecl = azslParser::NamedVariableDeclaratorContext;
- using AstUnnamedVarDecl = azslParser::UnnamedVariableDeclaratorContext;
- using AstFuncSig = azslParser::LeadingTypeFunctionSignatureContext;
- using AstIdExpr = azslParser::IdExpressionContext;
- using AstExpr = azslParser::ExpressionContext;
- using AstMemberAccess = azslParser::MemberAccessExpressionContext;
- using AstVarInitializer = azslParser::VariableInitializerContext;
- inline constexpr ssize_t operator ""_ssz(unsigned long long n)
- {
- return n;
- }
- inline bool EqualNoCase(string_view str1, string_view str2)
- {
- if (str1.length() != str2.length())
- {
- return false;
- }
- return AzslcStrnicmp(str1.data(), str2.data(), str2.length()) == 0;
- }
- inline bool StartsWithNoCase(string_view str1, string_view str2)
- {
- if (str1.length() < str2.length())
- {
- return false;
- }
- return AzslcStrnicmp(str1.data(), str2.data(), str2.length()) == 0;
- }
- // this format is the Microsoft standard for error list parsing "file(line,column): message"
- inline string DiagLine(size_t line)
- {
- using namespace std::string_literals;
- return AzslcException::s_lineFinder->GetVirtualFileName(line) + "("s + std::to_string(line) + "):";
- }
- inline string DiagLine(optional<int> line)
- {
- return line ? DiagLine(static_cast<size_t>(*line)) : string{};
- }
- inline string DiagLine(optional<size_t> line)
- {
- return line ? DiagLine(*line) : string{};
- }
- inline string DiagLine(antlr4::Token* token)
- {
- return DiagLine(token->getLine());
- }
- inline string DiagLine(tree::TerminalNode* astNode)
- {
- return DiagLine(astNode->getSymbol());
- }
- //! low level version with everything parameterizable
- template<typename... Types>
- inline void PrintWarning(DiagnosticStream& stream, Warn::EnumType level, optional<size_t> lineNumber, optional<size_t> column, Types&&... messageBits)
- {
- stream << PushLevel{} << level
- << AzslcException::MakeErrorMessage(lineNumber ? AzslcException::s_lineFinder->GetVirtualFileName(*lineNumber) : "",
- lineNumber ? ToString(AzslcException::s_lineFinder->GetVirtualLineNumber(*lineNumber)) : "", column ? ToString(*column) : "",
- "", false, "", ConcatString(messageBits..., "\n"))
- << PopLevel{};
- }
- //! version for clients with only, maybe, a line number
- template<typename... Types>
- inline void PrintWarning(Warn::EnumType level, optional<size_t> line, Types&&... messageBits)
- {
- PrintWarning(warningCout, level, line, none, messageBits...);
- }
- //! version for clients with a token (richest, preferred way)
- template<typename... Types>
- inline void PrintWarning(Warn::EnumType level, antlr4::Token* token, Types&&... messageBits)
- {
- PrintWarning(warningCout, level, token->getLine(), token->getCharPositionInLine() + 1, messageBits...);
- }
- inline AstTypeofNode* ExtractTypeofAstNode(AstType* ctx)
- {
- return ctx->typeofExpression();
- }
- template<typename AnyOther>
- inline AstTypeofNode* ExtractTypeofAstNode(AnyOther*)
- {
- return nullptr;
- }
- using ConstNumericVal = variant<monostate, int32_t, uint32_t, float>;
- //! Extracts <float> from <ConstNumericVal>. When not possible it will throw if <defval> is not set or fall back to <defval> if set.
- inline float ExtractValueAsFloat(const ConstNumericVal& var, optional<float> defval = none)
- {
- if (holds_alternative<monostate>(var))
- {
- if (defval == none)
- {
- throw std::logic_error{ "Constant value did not hold anything. Set defval if you want a fallback option." };
- }
- else
- {
- return *defval;
- }
- }
- else if (holds_alternative<int32_t>(var))
- {
- auto intVal = get<int32_t>(var);
- // Casting to float has precision loss: https://onlinegdb.com/By0AJTUVE
- if (intVal > 16777216 || intVal < -16777216)
- {
- PrintWarning(Warn::W3, none, "warning: Casting integer ", intVal, " to float, will result in ", static_cast<float>(intVal));
- }
- return static_cast<float>(intVal);
- }
- else if (holds_alternative<uint32_t>(var))
- {
- auto uintVal = get<uint32_t>(var);
- // Same comment
- if (uintVal > 16777216)
- {
- PrintWarning(Warn::W3, none, "warning: Casting integer ", uintVal, " to float, will result in ", static_cast<float>(uintVal));
- }
- return static_cast<float>(uintVal);
- }
- assert(holds_alternative<float>(var));
- return get<float>(var);
- }
- inline int64_t ExtractValueAsInt64(const ConstNumericVal& var, optional<int64_t> defval = none)
- {
- if (holds_alternative<monostate>(var))
- {
- if (defval == none)
- {
- throw std::logic_error{ "Constant value did not hold anything. Set defval if you want a fallback option." };
- }
- else
- {
- return *defval;
- }
- }
- else if (holds_alternative<float>(var))
- {
- // Casting from float has precision loss: https://onlinegdb.com/By0AJTUVE
- auto floatVal = get<float>(var);
- auto intVal = static_cast<int64_t>(floatVal);
- PrintWarning(Warn::W3, none, "warning: Casting float ", floatVal, " to integer, will result in ", intVal);
- return intVal;
- }
- else if (holds_alternative<int32_t>(var))
- {
- return get<int32_t>(var);
- }
- return static_cast<int64_t>(get<uint32_t>(var));
- }
- template<class T>
- inline T ExtractValueAs(const ConstNumericVal& var, optional<T> defval = none)
- {
- if (holds_alternative<monostate>(var))
- {
- if (defval == none)
- {
- throw std::logic_error{ "Constant value did not hold anything. Set defval if you want a fallback option." };
- }
- else
- {
- return *defval;
- }
- }
- else if (holds_alternative<float>(var))
- {
- // Casting from float has precision loss: https://onlinegdb.com/By0AJTUVE
- auto floatVal = get<float>(var);
- auto intVal = static_cast<int64_t>(floatVal);
- PrintWarning(Warn::W3, none, "warning: Casting float ", floatVal, " to integer, will result in ", intVal);
- return static_cast<T>(intVal);
- }
- else if (holds_alternative<int32_t>(var))
- {
- return static_cast<T>(get<int32_t>(var));
- }
- return static_cast<T>(get<uint32_t>(var));
- }
- //! Safe way to call ExtractValueAsInt64 which returns false instead of throwing
- inline bool TryGetConstExprValueAsInt64(const ConstNumericVal& foldedIdentifier, int64_t& returnValue) noexcept(true)
- {
- if (holds_alternative<monostate>(foldedIdentifier))
- {
- return false;
- }
- returnValue = ExtractValueAsInt64(foldedIdentifier); // Won't throw because we have already checked holds_alternative<monostate>
- return true;
- }
- MAKE_REFLECTABLE_ENUM_POWER (StorageFlag,
- Static, Const, Unsigned, RowMajor, ColumnMajor, Extern, Inline, Rootconstant, Option, Precise, Groupshared, Uniform, Volatile, Globallycoherent, In, Out, InOut, Enumerator, Other
- );
- inline Streamable& operator << (Streamable& out, StorageFlag::EnumType sf)
- {
- return out << ToLower(StorageFlag::ToStr(sf));
- }
- using Modifiers = Flag<StorageFlag>;
- struct TypeQualifiers
- {
- Modifiers m_flag;
- vector<string> m_others; // For qualifiers we didn't add to the enum
- string GetDisplayName() const
- {
- vector<StorageFlag> bag;
- auto end = std::copy_if(StorageFlag::Enumerate{}.begin(), StorageFlag::Enumerate{}.end(), std::back_inserter(bag),
- [&](auto sf) -> bool { return (m_flag & sf) && (sf & ~StorageFlag::Other); });
- // Join will call operator<< on StorageFlag::EnumType for stringification
- return string{Trim(Join(bag.begin(), bag.end(), " ") + " " + Join(m_others.begin(), m_others.end(), " "))};
- }
- void OrMerge(const TypeQualifiers& src)
- {
- m_flag |= src.m_flag;
- StableMerge(m_others, src.m_others); // stable is key to not disturb emission tests accross platforms that could hash differently if using unordered_sets.
- }
- };
- struct ArrayDimensions
- {
- const bool IsArray() const
- {
- return !m_dimensions.empty();
- }
- const bool IsUnbounded() const
- {
- // For now m_tex[][4][5] won't be supported.
- // But m_tex[] is supported.
- return (m_dimensions.size() == 1) && (m_dimensions[0] == Unbounded);
- }
- //! Have all dimensions been statically resolved ?
- const bool AreAllDimsFullyConstantFolded() const
- {
- return std::all_of(m_dimensions.begin(), m_dimensions.end(), [](int d) { return (d >= 0); });
- }
- int GetDimensionAt_OrDefault(int dimensionIndex, int defaultValueIfNoSuchDim) const
- {
- return m_dimensions.size() > dimensionIndex ? m_dimensions[dimensionIndex] : defaultValueIfNoSuchDim;
- }
- //! pretty print a representation of an array variable declarator suffix in C-like form
- //! {} is ""
- //! {unbounded} is "[]"
- //! {unknown} is "[?]"
- //! {1,2} is "[1][2]"
- string ToString(const string& prefix = "[", const string& separator = "][", const string& suffix = "]") const
- {
- vector<string> asStrs;
- TransformCopy(m_dimensions, asStrs,
- [](int d) -> string { return d == Unknown ? "<unrecognized-expr>" : d == Unbounded ? "" : std::to_string(d); });
- return asStrs.empty() ? "" : prefix + Join(asStrs.begin(), asStrs.end(), separator) + suffix;
- }
- void PushBack(int newDimension)
- {
- m_dimensions.push_back(newDimension);
- }
- void Clear()
- {
- m_dimensions.clear();
- }
- bool Empty() const
- {
- return m_dimensions.empty();
- }
- auto begin() const
- {
- return m_dimensions.begin();
- }
- auto end() const
- {
- return m_dimensions.end();
- }
- friend bool operator == (const ArrayDimensions& lhs, const ArrayDimensions& rhs)
- {
- return lhs.m_dimensions == rhs.m_dimensions;
- }
- friend bool operator != (const ArrayDimensions& lhs, const ArrayDimensions& rhs)
- {
- return !operator==(lhs,rhs);
- }
- //! value taken by dimensions that couldn't be constant-folded (variable initializer)
- static constexpr int Unknown = -1;
- //! dimension with an empty bracket []
- static constexpr int Unbounded = -2;
- //! multi array dimensions. e.g. a[2][3] will be a vector of {2,3}
- //! a[][3] (or a[var][3]) will be a vector of {unknown,3}
- //! (note: if `var` is a statically foldable const expression, then it has a chance to resolved to its initial value)
- //! empty {} means "not an array"
- vector<int> m_dimensions;
- };
- //! OutputFormat
- //! Specifies the pixel output format hint for the render target
- //!
- //! None - hints that the target is unused
- //! R32 - one channel (R), can be Float32, Uint32 or Sint32
- //! R32G32 - two channels (RG), can be Float32, Uint32 or Sint32
- //! R32A32- two channels (RA), can be Float32, Uint32 or Sint32
- //! R16G16B16A16_FLOAT - Default. Four or less channels (RGBA), Float16
- //! R16G16B16A16_UNORM - four or less channels (RGBA), Unorm16
- //! R16G16B16A16_SNORM - four or less channels (RGBA), Snorm16
- //! R16G16B16A16_UINT - four or less channels (RGBA), Uint16
- //! R16G16B16A16_SINT - four or less channels (RGBA), Sint16
- //! R32G32B32A32 - four channels (RGBA), can be Float32, Uint32 or Sint32
-
- MAKE_REFLECTABLE_ENUM(OutputFormat,
- None, R32, R32G32, R32A32, R16G16B16A16_FLOAT, R16G16B16A16_UNORM, R16G16B16A16_SNORM, R16G16B16A16_UINT, R16G16B16A16_SINT, R32G32B32A32
- );
- namespace Packing
- {
- constexpr uint32_t s_bytesPerRegister = 16;
- constexpr uint32_t s_bytesPerComponent = 4;
- enum class Layout : uint32_t
- {
- CStylePacking, //!< Dense packing with no padding. Also known as Scalar. Default for structured buffers in DirectX.
- DirectXPacking, //!< DirectX style packing, default for cbuffer layouts in DirectX
- RelaxedDirectXPacking, //!< As DirectX standard, but assumes dense array packing and invariant to row-column major (best fit)
- RelaxedStd140Packing, //!< Vector-relaxed OpenGL std140, default for Uniform buffer packing for Vulkan (base alignment)
- RelaxedStd430Packing, //!< Vector-relaxed OpenGL std430, default for Storage buffer packing for Vulkan (scalar alignment)
- StrictStd140Packing, //!< Strict OpenGL std140, default for Uniform buffer packing for OpenGL
- StrictStd430Packing, //!< Strict OpenGL std430, default for Storage buffer packing for OpenGL
- DirectXStoragePacking = CStylePacking, //!< DirectX style packing for Storage buffers. That's actually Scalar
- };
- //! Used together with Layout. Defines what alignment rules the next chunk should follow
- enum class Alignment : uint32_t
- {
- asVectorStart,
- asVectorEnd,
- asMatrixStart,
- asMatrixEnd,
- asStructStart,
- asStructEnd,
- asArrayStart,
- asArrayEnd,
- };
- // Some standards (std140/std430) have extended alignment rules for structures, which dictate that
- // the alignment inside a struct behaves like base rather than scalar.
- // For all other cases the alignment doesn't change so we can return the input.
- static Layout GetExtendedLayout(const Layout& scalarLayout)
- {
- if (scalarLayout == Layout::RelaxedStd430Packing)
- {
- return Layout::RelaxedStd140Packing;
- }
- else if (scalarLayout == Layout::StrictStd430Packing)
- {
- return Layout::StrictStd140Packing;
- }
- return scalarLayout;
- }
- static uint32_t AlignUp(const uint32_t value, const uint32_t alignment)
- {
- if (alignment <= 1)
- {
- return value;
- }
- uint32_t mask = alignment - 1;
- return (value + mask) & ~mask;
- }
- static uint32_t AlignStructToLargestMember(Layout layout, uint32_t currentSize, uint32_t memberSize)
- {
- if (memberSize == 0)
- {
- return currentSize;
- }
- if (layout == Layout::RelaxedStd140Packing ||
- layout == Layout::RelaxedStd430Packing ||
- layout == Layout::StrictStd140Packing ||
- layout == Layout::StrictStd430Packing)
- {
- // Alignment starts as s_bytesPerComponent, can only double (power of two) and cannot exceed s_bytesPerRegister
- auto alignment = s_bytesPerComponent;
- while (alignment < memberSize && alignment < s_bytesPerRegister)
- {
- alignment *= 2;
- }
- return AlignUp(currentSize, alignment);
- }
- return currentSize;
- }
- // Checks if the parameters correspond to either a Vector or a Matrix collapsed to a Vector.
- static bool IsVectorAligned(const Alignment& alignment, uint32_t rows, uint32_t cols)
- {
- if (alignment == Alignment::asVectorStart ||
- alignment == Alignment::asVectorEnd)
- {
- return true;
- }
- if (alignment == Alignment::asMatrixStart ||
- alignment == Alignment::asMatrixEnd)
- {
- if (rows <= 1 || cols <= 1)
- {
- return true;
- }
- }
- return false;
- }
- //! Aligns the offset for the next structure
- //! Vulkan : https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#interfaces-resources-layout
- //! https://github.com/microsoft/DirectXShaderCompiler/blob/master/docs/SPIR-V.rst#memory-layout-rules
- //! OpenGL : https://www.khronos.org/registry/OpenGL/specs/gl/glspec45.core.pdf#page=159
- //! https://github.com/Microsoft/DirectXShaderCompiler/blob/master/docs/SPIR-V.rst
- static uint32_t AlignOffset(Layout layout, uint32_t currentSize, const Alignment& alignment, uint32_t rows, uint32_t cols)
- {
- switch (layout)
- {
- case Layout::CStylePacking:
- return currentSize;
- case Layout::DirectXPacking:
- case Layout::RelaxedDirectXPacking:
- if (alignment == Alignment::asMatrixStart ||
- alignment == Alignment::asArrayStart ||
- alignment == Alignment::asStructStart)
- {
- return AlignUp(currentSize, s_bytesPerRegister);
- }
- // Vectors, matrix endings or array endings don't require special alignment
- return currentSize;
- case Layout::RelaxedStd140Packing:
- if (alignment == Alignment::asArrayStart ||
- alignment == Alignment::asStructStart ||
- alignment == Alignment::asArrayEnd ||
- alignment == Alignment::asStructEnd )
- {
- return AlignUp(currentSize, s_bytesPerRegister);
- }
- if (IsVectorAligned(alignment, rows, cols))
- {
- // Matrices with only one row or only one column collapse to vectors and don't require alignment if the size fits the remaining register
- // const auto elements = std::max(rows, cols);
- // According to the base alignment packing rules vectors with 3 elements align as 4-element vectors with strict std140.
- // dxc doesn't seem to follow that standard with relaxed std140.
- return currentSize;
- }
- if (alignment == Alignment::asMatrixStart ||
- alignment == Alignment::asMatrixEnd)
- {
- return AlignUp(currentSize, s_bytesPerRegister);
- }
- return currentSize;
- case Layout::RelaxedStd430Packing:
- case Layout::StrictStd140Packing:
- case Layout::StrictStd430Packing:
- // The relaxed std430 (= scalar packing) for Vulkan doesn't dictate any special rules for matrix, array or struct
- // start or end decorations, but individual case are handled uniquely
- // dxc is also not without exceptions: https://github.com/microsoft/DirectXShaderCompiler/issues/2639
- // so fully handling complex structs might take more effort
- return currentSize;
- default:
- throw std::runtime_error{ "PackNextChunk: Unknown format should be handled properly!" };
- break;
- }
- return 0; // Prevents warning C4715
- }
- //! Packs the next chunk of data to the current offset and returns the new offset
- //! Alignment - dictates the alignment rules to follow for packing nextChunkSize
- static uint32_t PackNextChunk(Layout layout, uint32_t nextChunkSize, uint32_t& offset)
- {
- switch (layout)
- {
- case Layout::CStylePacking:
- return offset + nextChunkSize;
- case Layout::DirectXPacking:
- case Layout::RelaxedDirectXPacking:
- case Layout::RelaxedStd140Packing:
- case Layout::StrictStd140Packing:
- {
- // Rule - sizes within 16 bytes should not cross the 16-byte boundary
- const auto remaining = offset % s_bytesPerRegister;
- if (remaining > 0 && remaining + nextChunkSize > s_bytesPerRegister)
- {
- offset = AlignUp(offset, s_bytesPerRegister);
- }
- return offset + nextChunkSize;
- }
- case Layout::RelaxedStd430Packing:
- case Layout::StrictStd430Packing:
- return offset + nextChunkSize;
- default:
- throw std::runtime_error{ "PackNextChunk: Unknown format should be handled properly!" };
- break;
- }
- return 0; // Prevents warning C4715
- }
- //! Packs the base size into an array of certain dimensions
- //! Dimensions can be 0, in which case it returns the baseSize, because there is no array rules to follow
- //! out Alignment - The minimum alignment required to pack this structure
- static uint32_t PackIntoArray(Layout layout, uint32_t baseSize, const ArrayDimensions& dimensions)
- {
- if (dimensions.Empty())
- {
- return baseSize;
- }
- // Sanity check. We can't do this earlier as some array dimensions are not resolvable at compile time,
- // but still valid at runtime.
- if (!dimensions.AreAllDimsFullyConstantFolded())
- {
- throw std::runtime_error{"Layout packing failed for unresolved array dimensions. Please check previous warnings!"};
- }
- switch (layout)
- {
- case Layout::CStylePacking:
- case Layout::RelaxedDirectXPacking:
- case Layout::RelaxedStd430Packing:
- case Layout::StrictStd430Packing:
- case Layout::StrictStd140Packing:
- {
- uint32_t totalElements = 1;
- for (auto dimension : dimensions)
- {
- totalElements *= dimension;
- }
- return baseSize * totalElements;
- }
- case Layout::DirectXPacking:
- {
- // DirectX elements always start on a new register
- const auto unalignedSize = baseSize;
- baseSize = AlignUp(baseSize, s_bytesPerRegister);
- uint32_t totalElements = 1;
- for (auto dimension : dimensions)
- {
- totalElements *= dimension;
- }
- return baseSize * (totalElements - 1) + unalignedSize; // The last element doesn't occupy its full register
- }
- case Layout::RelaxedStd140Packing:
- {
- baseSize = AlignUp(baseSize, s_bytesPerRegister);
- uint32_t totalElements = 1;
- for (auto dimension : dimensions)
- {
- totalElements *= dimension;
- }
- return baseSize * totalElements;
- }
- default:
- throw std::runtime_error{ "PackNextChunk: Unknown format should be handled properly!" };
- break;
- }
- return 0; // Prevents warning C4715
- }
- //! Packs the base size as a vector or a matrix
- //! Rows and/or cols can be 0. However, if Rows is greater than 0, Cols cannot be 0.
- //! Note! Regardless of major, matrices are defined as rows-by-columns! Rows == 0 means it's not a matrix
- static uint32_t PackAsVectorMatrix(Layout layout, uint32_t baseSize, uint32_t rows, uint32_t cols, bool rowMajor)
- {
- if (cols <= 1 && rows <= 1)
- { // This is neither - return
- return baseSize;
- }
- // Sanity check - if it's a matrix then rows > 0 and cols must be in the [1..4] range
- if (rows > 0)
- {
- // https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/dx-graphics-hlsl-per-component-math
- assert(rows <= 4);
- assert(cols >= 1 && cols <= 4);
- }
- switch (layout)
- {
- case Layout::CStylePacking:
- return baseSize
- * (cols == 0 ? 1 : cols)
- * (rows == 0 ? 1 : rows);
- case Layout::DirectXPacking:
- if (rows > 0)
- { // It's a matrix
- if (rowMajor && rows > 1 && cols == 1)
- {
- // This is a very special case which we can't replicate the same way dxc packs the data
- // Additionally, this results in a lot of wasted space, so it's explicitly forbidden
- throw std::runtime_error{ "PackAsVectorMatrix: row_major packing for 2x1, 3x1 and 4x1 matrix types is not allowed!" };
- }
- const auto fullSize = ((rowMajor) ? rows : cols) * s_bytesPerRegister; // Use Rows or Cols to calculate the size
- // Note! A matrix does not occupy the entire last register if its number of elements is less than 4
- const auto adjustSize = (4 - ((rowMajor) ? cols : rows)) * s_bytesPerComponent; // Use the opposite (Cols or Rows)
- return fullSize - adjustSize;
- }
- // It's a vector
- return cols * s_bytesPerComponent;
- case Layout::RelaxedDirectXPacking:
- if (rows > 0)
- { // It's a matrix
- auto major = (rows < cols) ? rows : cols; // Take the smaller fit
- return major * s_bytesPerRegister;
- }
- // It's a vector
- return cols * s_bytesPerComponent;
- case Layout::RelaxedStd140Packing:
- case Layout::RelaxedStd430Packing:
- case Layout::StrictStd140Packing:
- case Layout::StrictStd430Packing:
- if (rows > 0)
- { // It's a matrix
- if (rows == 1)
- {
- // It collapses it as a vector
- return cols * s_bytesPerComponent;
- }
- if (cols == 1)
- {
- // It collapses it as a vector
- return rows * s_bytesPerComponent;
- }
- auto matrixStride = (layout == Layout::RelaxedStd430Packing && rows == 2) ? 2 * s_bytesPerComponent : s_bytesPerRegister;
- auto matrixElements = rowMajor ? rows : cols;
- if (rowMajor)
- {
- std::swap(matrixStride, matrixElements);
- }
- return matrixStride * matrixElements;
- }
- // It's a vector
- return cols * s_bytesPerComponent;
- }
- return 0; // Prevents warning C4715
- }
- inline uint32_t PackedSizeof(int indexInAzslPredefined_Scalar)
- {
- // the array is generated but it's expected to look like:
- // {"bool", "double", "dword", "float", "half", "int", "int16_t", "int32_t", "int64_t", "uint", "uint16_t", "uint32_t", "uint64_t"}
- // just update that code if it changes one day, the assert will pop.
- if (indexInAzslPredefined_Scalar == 1 || indexInAzslPredefined_Scalar == 8 || indexInAzslPredefined_Scalar == 12)
- {
- assert(string_view{"double"} == AZ::ShaderCompiler::Predefined::Scalar[1]);
- assert(string_view{"int64_t"} == AZ::ShaderCompiler::Predefined::Scalar[8]);
- assert(string_view{"uint64_t"} == AZ::ShaderCompiler::Predefined::Scalar[12]);
- // Shader packing reference:
- // https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/dx-graphics-hlsl-packing-rules
- return 8;
- }
- else if (indexInAzslPredefined_Scalar == 4 || indexInAzslPredefined_Scalar == 6 || indexInAzslPredefined_Scalar == 10)
- {
- // https://github.com/microsoft/DirectXShaderCompiler/wiki/Buffer-Packing
- // extract: "with -enable-16bit-types: HLSL half type maps to native 16-bit float16_t type"
- // "native 16-bit types have storage size of 16-bits (as expected)"
- assert(string_view{"half"} == AZ::ShaderCompiler::Predefined::Scalar[4]);
- assert(string_view{"int16_t"} == AZ::ShaderCompiler::Predefined::Scalar[6]);
- assert(string_view{"uint16_t"} == AZ::ShaderCompiler::Predefined::Scalar[10]);
- return 2;
- }
- else if (indexInAzslPredefined_Scalar < 0)
- {
- return 0; // non-predefined case, surely meaning UDT.
- }
- assert(indexInAzslPredefined_Scalar < AZ::ShaderCompiler::Predefined::Scalar.size()); // #craefulgang.
- return 4; // bool is 32 too.
- }
- };
- struct TokensLocation
- {
- misc::Interval m_expressionSpan; // eg for `void F()` -> type id-expr brace brace -> m_expressionSpan concerns id-expr. for `nested::leaf` -> all 3 tokens are in the span
- ssize_t m_focusedTokenId; // eg for `void F()` -> `F` is the focused token. for id-expr, each nested identifier has a seenat. in `nested::leaf` -> 2 TokensLocation. one with 'nested' as focus, one with 'leaf' as focus; both with the same expression span.
- size_t m_line;
- size_t m_charPos;
- };
- // Most contexts (all?) will duck-type into this function
- template <typename ContextT>
- TokensLocation MakeTokensLocation(ContextT* ctx, const Token* focusedToken)
- {
- TokensLocation tl;
- tl.m_expressionSpan = const_cast<std::remove_const_t<ContextT>*>(ctx)->getSourceInterval();
- tl.m_focusedTokenId = static_cast<ssize_t>(focusedToken->getTokenIndex());
- tl.m_line = focusedToken->getLine();
- tl.m_charPos = focusedToken->getCharPositionInLine();
- return tl;
- };
- // version for single token
- inline TokensLocation MakeTokensLocation(const Token* tok)
- {
- TokensLocation tl;
- tl.m_expressionSpan = misc::Interval{tok->getTokenIndex(), tok->getTokenIndex()};
- tl.m_focusedTokenId = static_cast<ssize_t>(tok->getTokenIndex());
- tl.m_line = tok->getLine();
- tl.m_charPos = tok->getCharPositionInLine();
- return tl;
- };
- struct Seenat
- {
- IdentifierUID m_referredDefinition;
- TokensLocation m_where; // location in original stream, of a token group that can refer to a symbol.
- // (it's an interval because of nested-name-specifiers)
- };
- // before being able to clean that up with C++20 concepts, let's factorize the SFINAE expression to filter the accepted iterator types by an expected trait
- #define SFINAE_IS_SAME(DependentTypeToCompare, ExpectedType)\
- enable_if_t< is_same_v<typename DependentTypeToCompare, ExpectedType> >* = nullptr
- // Create a yaml element list for 'any collection' of Seenat (passed as a pseudo range)
- // - {line: x, col: y}
- template <typename SeenatRangeIter, SFINAE_IS_SAME(SeenatRangeIter::value_type, Seenat) >
- string ToYaml(SeenatRangeIter begin, SeenatRangeIter end, string_view indent)
- {
- string s;
- for (SeenatRangeIter it = begin; it != end; ++it)
- {
- s += indent;
- s += "- {line: " + std::to_string(it->m_where.m_line);
- s += ", col: " + std::to_string(it->m_where.m_charPos + 1); // +1 because charPos starts at 0. most editors start at 1
- s += "}\n";
- }
- return s;
- }
- // version for pseudo-range of IdentifierUID elements
- template <typename UIDRangeIter, SFINAE_IS_SAME(UIDRangeIter::value_type, IdentifierUID) >
- string ToYaml(UIDRangeIter begin, UIDRangeIter end, string_view indent)
- {
- string s;
- for (UIDRangeIter it = begin; it != end; ++it)
- {
- s += indent;
- s += "- {name: '" + it->m_name + "'}\n";
- }
- return s;
- }
- #undef SFINAE_IS_SAME
- // like PathPart but more specialized for grammatical elements specificity of an idExpression
- struct IdExpressionPart
- {
- string GetText() const
- {
- return m_token->getText();
- }
- bool IsScopeToken() const
- {
- return m_type == GlobalScopeOperator || m_type == ScopeResolutionOperator;
- }
- // Azslc Intermediate Representation uses slash separators for scopes
- // This function can help constructing internal "mangled" forms of qualified symbol names
- string GetAZIRMangledText() const
- {
- return IsScopeToken() ? "/" : GetText();
- }
- enum Type
- {
- NestedNameSpecifier, // nns::leaf (nns)
- ScopeResolutionOperator, // nns::leaf (::)
- GlobalScopeOperator, // ::leaf (leading ::)
- LoneUnqualifiedId, // leaf (leaf)
- QualifiedLeaf // nns::leaf (leaf)
- };
- Token* m_token;
- Type m_type;
- };
- template <typename Functor>
- void ForEachIdExpressionPart(AstIdExpr* ctx, Functor action)
- {
- static_assert( is_invocable<Functor, IdExpressionPart>::value, "please pass function objects that takes IdExpressionPart as argument" );
- if (ctx->unqualifiedId())
- {
- Token* loneLeaf = ctx->unqualifiedId()->Identifier()->getSymbol();
- action(IdExpressionPart{loneLeaf, IdExpressionPart::LoneUnqualifiedId});
- }
- else
- {
- Token* globalSROToken = ctx->qualifiedId()->nestedNameSpecifier()->GlobalSROToken;
- if (globalSROToken)
- {
- action(IdExpressionPart{globalSROToken, IdExpressionPart::GlobalScopeOperator});
- }
- for (auto* identifier : ctx->qualifiedId()->nestedNameSpecifier()->Identifier())
- {
- action(IdExpressionPart{identifier->getSymbol(), IdExpressionPart::NestedNameSpecifier});
- auto* nextToken = identifier->getSymbol()->getTokenSource()->nextToken().get();
- action(IdExpressionPart{nextToken, IdExpressionPart::ScopeResolutionOperator});
- }
- Token* last = ctx->qualifiedId()->unqualifiedId()->Identifier()->getSymbol();
- action(IdExpressionPart{last, IdExpressionPart::QualifiedLeaf});
- }
- }
- // This is a helper to make client code look smoother (free of internal detail about branching in non-null tree paths...)
- // It will reconstruct the mangled string for the identifier by looping over nested name elements.
- // The return type is Unqualified, even if a global SRO token is present.
- // When a global SRO token is present (leading :: in an idExression) then the identifier
- // is necessarily fully qualified (FQ).
- // We assume all identifiers are relative (non FQ), but in this one case,
- // we know that it's FQ.
- // This is too little a case to go out of our way and construct a QualifiedName.
- // Even if that sounds cool and clean, it breaks the principle of canonicality.
- // The QualifiedName and UnqualifiedName types are made to make compile time assignment
- // incompatible.
- // But the distinction here would have to be made at runtime.
- // Effectively changing the return type to a variant<UnqualifiedName, QualifiedName>
- // .. losing the compile time advantage of the whole original design choice.
- // Not only that, but propagating this information upward will pollute the clients, who
- // will feel compelled to preserve this information like it was a precious bit of entropy.
- // Except.. it is not precious. We can treat a FQ symbol as unqualified,
- // because all names will have to go through the MakeFullyQualified cruncher anyway.
- // (and that cruncher is canonical enough to return identity when it sees a FQ symbol)
- // Making this choice, we streamline treatment of names and simplify code.
- inline UnqualifiedName ExtractNameFromIdExpression(azslParser::IdExpressionContext* ctx)
- {
- std::stringstream ss; // accumulator
- ForEachIdExpressionPart(ctx, [&ss](const IdExpressionPart& part) { ss << part.GetAZIRMangledText(); });
- return UnqualifiedName{ss.str()};
- }
- template <typename TemplateContext>
- UnqualifiedName ExtractNameFromAnyContextWithName(TemplateContext* ctx)
- {
- return UnqualifiedName{ctx->Name->getText()};
- }
- template <typename ParentType>
- bool Is3ParentRuleOfType(antlr4::ParserRuleContext* ctx)
- {
- if (ctx == nullptr || ctx->parent == nullptr || ctx->parent->parent == nullptr) // input canonicalization
- {
- return false;
- }
- auto threeUp = ctx->parent->parent->parent;
- return dynamic_cast<ParentType>(threeUp);
- }
- // is def
- inline bool IsParentRuleAFunctionDefinition(azslParser::FunctionParamContext* ctx)
- {
- return Is3ParentRuleOfType<azslParser::HlslFunctionDefinitionContext*>(ctx);
- }
- // is decl
- inline bool IsParentRuleAFunctionDeclaration(azslParser::FunctionParamContext* ctx)
- {
- return Is3ParentRuleOfType<azslParser::HlslFunctionDeclarationContext*>(ctx);
- }
- inline bool IsRHSOfMemberAccess(tree::ParseTree* ctx)
- {
- auto* asMemberAccess = As<AstMemberAccess*>(ctx->parent);
- return asMemberAccess && asMemberAccess->Member == ctx;
- }
- //! Checks if your current rule is at distance 0 of a rule you want to target; (according to pointer arithmetic distance)
- //! example with target = ArrayAccessExpresison
- //! $ ((T)a::b.c)[x] from c: unqualifiedId -> idExpression -> (right of) MemberAccessExpression -> CastExpression -> ParenthesizedExpression -> ArrayAccessExpression
- //! all rules involved are 0-away (transparent) for pointer arithmetic, thus-> return true
- //! from b: unqualifiedId -> qualifiedId -> idExpression -> (left of) MemberAccessExpression
- //! stop here. because LHS of 'dot'. the "pointer-like" operation is offseted by RHS. thus -> return false
- //! $ (c + d)[x] from any of c or d: the link is broken here because '+' introduced a distance, the subscript applies on the temporary that results. thus -> return false
- //! returns: the found target, or nullptr if not found or not 0-away
- template< typename TargetRule >
- inline TargetRule* FindRuleInAstThatIs0AwayWrtPointerDistance(antlr4::ParserRuleContext* ctx)
- {
- // possible intermediates (that don't introduce a distance):
- using Nested = azslParser::NestedNameSpecifierContext;
- using Qualif = azslParser::QualifiedIdContext;
- using UnQualif = azslParser::UnqualifiedIdContext;
- using IdExpr = azslParser::IdentifierExpressionContext;
- using BraceExpr = azslParser::ParenthesizedExpressionContext;
- using Cast = azslParser::CastExpressionContext;
- //and AstIdExpr, AstMemberAccess too
- if (ctx == nullptr)
- {
- return nullptr;
- }
- if (TargetRule* callCtx = Is<TargetRule*>(ctx) ? As<TargetRule*>(ctx) : As<TargetRule*>(ctx->parent)) // goal
- {
- return callCtx;
- }
- bool zeroDist = DynamicTypeIsAnyOf<Nested, Qualif, UnQualif, IdExpr, BraceExpr, Cast, AstIdExpr>(ctx)
- || IsRHSOfMemberAccess(ctx);
- auto* parentAsParserRuleCtx = polymorphic_downcast<ParserRuleContext*>(ctx->parent);
- return zeroDist ? FindRuleInAstThatIs0AwayWrtPointerDistance<TargetRule>(parentAsParserRuleCtx) // if still valid, recurse
- : nullptr;
- }
- //! checks if your current rule is at distance 0 of a subscript []; according to pointer arithmetic distance.
- //! refer to comment of function FindRuleInAstThatIs0AwayWrtPointerDistance for examples
- inline bool IsNextToArrayAccessExpression(antlr4::ParserRuleContext* ctx)
- {
- using AAExpr = azslParser::ArrayAccessExpressionContext; // end condition
- return FindRuleInAstThatIs0AwayWrtPointerDistance<AAExpr>(ctx);
- }
- //! checks if your current rule is at distance 0 of a function call syntax f(); according to pointer arithmetic distance
- //! example: $ ((T)a::b.c)(x)
- //! returns: the argument list of the found call expression, if found. nullptr otherwise
- inline azslParser::ArgumentListContext* GetArgumentListIfBelongsToFunctionCall(antlr4::ParserRuleContext* ctx)
- {
- using Call = azslParser::FunctionCallExpressionContext; // end condition
- Call* found = FindRuleInAstThatIs0AwayWrtPointerDistance<Call>(ctx);
- return found ? found->argumentList() : nullptr;
- }
- //! try to find a specific context type that this context would be a child of.
- template <typename LookedUp>
- inline LookedUp* ExtractSpecificParent(antlr4::ParserRuleContext* ctx)
- {
- if (ctx == nullptr)
- {
- return nullptr;
- }
- auto* asCtx = As<LookedUp*>(ctx);
- if (asCtx) // goal
- {
- return asCtx;
- }
- return ctx->parent == nullptr ? nullptr : ExtractSpecificParent<LookedUp>(polymorphic_downcast<ParserRuleContext*>(ctx->parent));
- }
- //! try to find a variable initializer that this context would be a child of.
- inline AstVarInitializer* ExtractInitializerParent(antlr4::ParserRuleContext* ctx)
- {
- return ExtractSpecificParent<AstVarInitializer>(ctx);
- }
- inline azslParser::FunctionParamContext* ParamContextOverUnnamedVariableDeclarator(AstUnnamedVarDecl* ctx)
- {
- return As<azslParser::FunctionParamContext*>(ctx->parent);
- }
- inline azslParser::VariableDeclarationContext* VarDeclContextOverVariableDeclarator(AstNamedVarDecl* ctx)
- {
- return As<azslParser::VariableDeclarationContext*>(ctx->parent->parent);
- }
- inline azslParser::TypeContext* ExtractTypeFromUnnamedVariableDeclarator(AstUnnamedVarDecl* ctx, azslParser::FunctionParamContext** funcParamContextOut = nullptr)
- {
- auto* paramCtx = ParamContextOverUnnamedVariableDeclarator(ctx);
- if (paramCtx != nullptr)
- {
- if (funcParamContextOut)
- {
- *funcParamContextOut = paramCtx;
- }
- return paramCtx->type();
- }
- auto* varDeclCtx = VarDeclContextOverVariableDeclarator(As<AstNamedVarDecl*>(ctx->parent));
- if (varDeclCtx != nullptr)
- {
- return varDeclCtx->type();
- }
- return nullptr;
- }
- /// move up the AST into parent rules to ry to get a name. in case of function parameters, name can be omitted so this function may return null
- inline Token* ExtractVariableNameIdentifier(AstUnnamedVarDecl* ctx)
- {
- return Is<AstNamedVarDecl>(ctx->parent) ? polymorphic_downcast<AstNamedVarDecl*>(ctx->parent)->Name
- : polymorphic_downcast<azslParser::FunctionParamContext*>(ctx->parent)->Name;
- }
- inline ParserRuleContext* GetParentIfIsNamedVarDecl_OtherwiseIdentity(AstUnnamedVarDecl* ctx)
- {
- if (!ctx || !Is<AstNamedVarDecl>(ctx->parent))
- {
- return ctx;
- }
- return polymorphic_downcast<AstNamedVarDecl*>(ctx->parent);
- }
- inline string ExtractVariableNameSamplerBodyDeclaration(azslParser::SamplerBodyDeclarationContext* ctx)
- {
- // parent1 is variableInitializer ; parent2 is unnamedVariableDeclarator
- return ExtractVariableNameIdentifier(polymorphic_downcast<AstUnnamedVarDecl*>(ctx->parent->parent))->getText();
- }
- inline bool TypeIsSamplerComparisonState(AstUnnamedVarDecl* ctx)
- {
- auto* typeCtx = ExtractTypeFromUnnamedVariableDeclarator(ctx);
- return typeCtx->predefinedType() &&
- typeCtx->predefinedType()->samplerStatePredefinedType() &&
- typeCtx->predefinedType()->samplerStatePredefinedType()->SamplerComparisonState();
- }
- inline azslParser::StorageFlagsContext* ExtractStorageFlagsFromUnnamedVariableDeclarator(AstUnnamedVarDecl* ctx)
- {
- return ExtractTypeFromUnnamedVariableDeclarator(ctx)->storageFlags();
- }
- inline StorageFlag AsFlag(azslParser::StorageFlagContext* ctx)
- {
- return ctx->Const() ? StorageFlag::Const
- : ctx->Extern() ? StorageFlag::Extern
- : ctx->Groupshared() ? StorageFlag::Groupshared
- : ctx->Precise() ? StorageFlag::Precise
- : ctx->Static() ? StorageFlag::Static
- : ctx->Uniform() ? StorageFlag::Uniform
- : ctx->Volatile() ? StorageFlag::Volatile
- : ctx->Globallycoherent() ? StorageFlag::Globallycoherent
- : ctx->RowMajor() ? StorageFlag::RowMajor
- : ctx->ColumnMajor() ? StorageFlag::ColumnMajor
- : ctx->In() ? StorageFlag::In
- : ctx->Out() ? StorageFlag::Out
- : ctx->Inout() ? StorageFlag::InOut
- : ctx->Inline() ? StorageFlag::Inline
- : ctx->Option() ? StorageFlag::Option
- : ctx->Rootconstant() ? StorageFlag::Rootconstant
- : ctx->Unsigned() ? StorageFlag::Unsigned
- // Everything else can still be stored, but won't be checked in any special way:
- // linear, centroid, noninterpolation, noperspective, sample, point, line, triangle, lineadk, triangleadj, indices, vertices, etc...
- : StorageFlag::Other;
- }
- inline bool IsFlag(azslParser::StorageFlagContext* ctx, StorageFlag flag)
- {
- return AsFlag(ctx) == flag;
- }
- // Either just a string, or string + its original source node.
- // keeping the source node allows for typeof(expression) resolution, and reversely keeping the name allows for clear strings during debugging.
- // Ext for extended.
- struct ExtractedTypeExt
- {
- UnqualifiedName m_name;
- AstType* m_node = nullptr;
- };
- // composed type parts. 'Buffer<float4>' will have 'Buffer' as core and 'float4' as genericParam
- struct ExtractedComposedType
- {
- ExtractedTypeExt m_core;
- ExtractedTypeExt m_genericParam; // note: could be made recursive one day. use unique_ptr here
- ArrayDimensions m_genericDimensions; // can expand generic dimensions too later. for MSTexture, IO patch, matrix<t,N,M> etc...
- };
- // Compose middleEnd configurations, wraps minimal user options from command line.
- struct MiddleEndConfiguration
- {
- int m_rootConstantsMaxSize;
- AZ::ShaderCompiler::Packing::Layout m_packConstantBuffers;
- AZ::ShaderCompiler::Packing::Layout m_packDataBuffers;
- bool m_isRowMajor;
- bool m_padRootConstantCB;
- bool m_skipAlignmentValidation;
- };
- ExtractedComposedType ExtractComposedTypeNamesFromAstContext(AstType* ctx, vector<tree::TerminalNode*>* genericDims = nullptr);
- // Oftentimes a type rule looks like: `genericTexture: TextureKeyword '<' type '>'` , this function extracts `type`.
- // Types parent of that ctx should have an AnalyzeClass that returns HasGenericParameter
- // for more power, this function can return an AstPointer to a type rule directly in case of availability.
- inline ExtractedComposedType ExtractComposedTypeNamesFromAstContext(AstPredefinedTypeNode* ctx, vector<tree::TerminalNode*>* genericDims = nullptr)
- {
- if (ctx->bufferPredefinedType())
- { // use aggregate initialization syntax to construct the return type (tops 4 elements, but here we fillup 2 only)
- return {ExtractedTypeExt{UnqualifiedName{ctx->bufferPredefinedType()->bufferType()->getText()}}, // m_core
- ExtractedTypeExt{UnqualifiedName{ctx->bufferPredefinedType()->scalarOrVectorOrMatrixType()->getText()}}}; // m_genericParam
- }
- else if (ctx->genericMatrixPredefinedType())
- {
- auto intLit1 = ctx->genericMatrixPredefinedType()->IntegerLiteral(0);
- auto intLit2 = ctx->genericMatrixPredefinedType()->IntegerLiteral(1);
- if (genericDims && intLit1 && intLit2)
- {
- genericDims->push_back(intLit1);
- genericDims->push_back(intLit2);
- }
- return {ExtractedTypeExt{UnqualifiedName{ctx->genericMatrixPredefinedType()->Matrix()->getText()}}, // m_core
- ExtractedTypeExt{UnqualifiedName{ctx->genericMatrixPredefinedType()->scalarType()->getText()}}}; // m_genericParam
- }
- else if (ctx->streamOutputPredefinedType())
- {
- auto* core = ctx->streamOutputPredefinedType()->streamOutputObjectType();
- AstType* genericCtx = ctx->streamOutputPredefinedType()->type();
- return {ExtractedTypeExt{UnqualifiedName{core->getText()}}, // m_core
- ExtractedTypeExt{UnqualifiedName{genericCtx->getText()}, genericCtx}}; // for the generic, since this is a type context, we will return it as a node, and let the client recurse if needed.
- }
- else if (ctx->structuredBufferPredefinedType())
- {
- auto* core = ctx->structuredBufferPredefinedType()->structuredBufferName();
- AstType* genericCtx = ctx->structuredBufferPredefinedType()->type();
- return {ExtractedTypeExt{UnqualifiedName{core->getText()}}, // m_core
- ExtractedTypeExt{UnqualifiedName{genericCtx->getText()}, genericCtx}}; // same as above
- }
- else if (ctx->genericTexturePredefinedType())
- {
- return {ExtractedTypeExt{UnqualifiedName{ctx->genericTexturePredefinedType()->textureType()->getText()}}, // m_core
- ExtractedTypeExt{UnqualifiedName{ctx->genericTexturePredefinedType()->scalarOrVectorType()->getText()}}}; // m_genericParam
- }
- else if (ctx->msTexturePredefinedType())
- {
- auto intLit = ctx->msTexturePredefinedType()->IntegerLiteral();
- if (genericDims && intLit)
- {
- genericDims->push_back(intLit);
- }
- return {ExtractedTypeExt{UnqualifiedName{ctx->msTexturePredefinedType()->textureTypeMS()->getText()}}, // m_core
- ExtractedTypeExt{UnqualifiedName{ctx->msTexturePredefinedType()->scalarOrVectorType()->getText()}}}; // m_genericParam
- }
- else if (ctx->genericVectorType())
- {
- auto intLit = ctx->genericVectorType()->IntegerLiteral();
- if (genericDims && intLit)
- {
- genericDims->push_back(intLit);
- }
- return {ExtractedTypeExt{UnqualifiedName{ctx->genericVectorType()->Vector()->getText()}}, // m_core
- ExtractedTypeExt{UnqualifiedName{ctx->genericVectorType()->scalarType()->getText()}}}; // m_genericParam
- }
- else if (ctx->constantBufferTemplated())
- {
- auto coreName = ctx->constantBufferTemplated()->CBCoreType->getText();
- AstType* genericCtx = ctx->constantBufferTemplated()->GenericTypeName;
- return {ExtractedTypeExt{UnqualifiedName{coreName}}, // m_core
- ExtractedTypeExt{UnqualifiedName{genericCtx->getText()}, genericCtx}}; // same as previous 2 comments above
- }
- // all other rules are getable without artefacts for sure from a simple getText
- return {ExtractedTypeExt{UnqualifiedName{ctx->getText()}}}; // only core, no generic part.
- }
- //! from a special rule: scalarOrVectorOrMatrixType
- inline ExtractedComposedType ExtractComposedTypeNamesFromAstContext(azslParser::ScalarOrVectorOrMatrixTypeContext* ctx, vector<tree::TerminalNode*>* genericDims = nullptr)
- {
- return {ExtractedTypeExt{UnqualifiedName{ctx->getText()}}};
- // for now there is no generic part, but mind it when you fix the grammar to support generic vector.
- }
- //! from userDefinedType context
- inline ExtractedComposedType ExtractComposedTypeNamesFromAstContext(azslParser::UserDefinedTypeContext* ctx, vector<tree::TerminalNode*>* genericDims = nullptr)
- {
- if (ctx->idExpression())
- {
- return {ExtractedTypeExt{ExtractNameFromIdExpression(ctx->idExpression())}};
- }
- assert(ctx->anyStructuredTypeDefinition());
- auto* anyStructLikeCtx = ctx->anyStructuredTypeDefinition();
- auto structName = VisitFirstNonNull([](auto* ctx) { return ExtractNameFromAnyContextWithName(ctx); },
- anyStructLikeCtx->classDefinition(),
- anyStructLikeCtx->interfaceDefinition(),
- anyStructLikeCtx->enumDefinition(),
- anyStructLikeCtx->structDefinition());
- return {ExtractedTypeExt{UnqualifiedName{structName}}};
- }
- //! from type context (next to highest level)
- inline ExtractedComposedType ExtractComposedTypeNamesFromAstContext(AstType* ctx, vector<tree::TerminalNode*>* genericDims /*= nullptr*/)
- {
- if (ctx->userDefinedType())
- {
- return ExtractComposedTypeNamesFromAstContext(ctx->userDefinedType(), genericDims);
- }
- else if (ctx->predefinedType())
- {
- return ExtractComposedTypeNamesFromAstContext(ctx->predefinedType(), genericDims);
- }
- else if (ctx->Void())
- {
- assert(string_view{AZ::ShaderCompiler::Predefined::Void[0]} == ctx->Void()->getText());
- return {UnqualifiedName{ctx->Void()->getText()}}; // "void"
- }
- // this could be a typeof, let's return the node for further resolve!
- return {ExtractedTypeExt{UnqualifiedName{ctx->getText()}, ctx}};
- }
- //! Parse an HLSL semantic from a context into (semantic name, semantic index, is system value)
- inline tuple<string, int, bool> ExtractHlslSemantic(azslParser::HlslSemanticContext* hlslSemantic)
- {
- // Semantic name and index
- auto semanticName = hlslSemantic->getText();
- size_t index = semanticName.find_last_not_of("0123456789") + 1;
- int semanticIndex = (index == semanticName.length()) ?
- 0 : std::stoi(semanticName.substr(index));
- auto colon = semanticName.find_first_of(":");
- auto firstChar = (colon == string::npos) ? 0 : colon + 1;
- firstChar = semanticName.find_first_not_of(" ", firstChar);
- semanticName = semanticName.substr(firstChar, index - firstChar);
- bool isSystemValue = (hlslSemantic->Name->HLSLSemanticSystem() != nullptr);
- return { semanticName, semanticIndex, isSystemValue };
- }
- inline bool HasStandardInitializer(AstVarInitializer* ctx)
- {
- return ctx && ctx->standardVariableInitializer();
- }
- inline bool HasStandardInitializer(AstUnnamedVarDecl* ctx)
- {
- return ctx && HasStandardInitializer(ctx->variableInitializer());
- }
- }
|