Bladeren bron

Atom/galibzon/pad to n (#18)

* Add [[pad_to(N)]] language feature

This is version 1.7.34

"struct", "class" and "ShaderResourceGroup" definitions now support the [[pad_to(N)]] attribute.
This attribute, when used, must be declared after a member variable.
Each member variable can have one of these attributes after it.

The attribute is useful to pad the data with dummy variables until
guaranteeing that the offset of the next variable starts with the
desired offset. The 'N' argument is a single integral literal
that must be a multiple of 4.

[[pad_to(N)]] requires N to be a power of two if the next starting
offset is bigger than N.

A companion class to the IntermediateRepresentation has been added:
PadToAttributeMutator. It contains all the logic and algorithms to
collect, during parsing, the variables that require padding
and apply the appropriate mutations (variable insertions) during
the IntermediateRepresentation::MiddleEnd().

Added ExtractValueAs<T> template function for extracting the value
of a ConstNumericalVal. (Need to be revisited).

Signed-off-by: garrieta <[email protected]>
galibzon 3 jaren geleden
bovenliggende
commit
8dbbc3ad0d

+ 3 - 0
src/AzslcException.h

@@ -82,6 +82,9 @@ namespace AZ::ShaderCompiler
         IR_NO_FALLBACK_ASSIGNED = 129u,
         IR_SRG_WITHOUT_SEMANTIC = 130u,
         IR_POTENTIAL_DX12_VS_VULKAN_ALIGNMENT_ERROR = 131u,
+        IR_INVALID_PAD_TO_ARGUMENTS = 132u,
+        IR_INVALID_PAD_TO_LOCATION = 133u,
+        IR_PAD_TO_CASE_REQUIRES_POWER_OF_TWO = 134u,
 
         // emitter error codes
         EMITTER_INVALID_ARRAY_DIMENSIONS = 256u,

+ 64 - 4
src/AzslcIntermediateRepresentation.cpp

@@ -62,11 +62,13 @@ namespace AZ::ShaderCompiler
             }
         }
 
-        ProcessAttributeSpecifier(attrInfo);
-        m_symbols.PushPendingAttribute(attrInfo, scope);
+        if (ProcessAttributeSpecifier(attrInfo))
+        {
+            m_symbols.PushPendingAttribute(attrInfo, scope);
+        }
     }
 
-    void IntermediateRepresentation::ProcessAttributeSpecifier(const AttributeInfo& attrInfo)
+    bool IntermediateRepresentation::ProcessAttributeSpecifier(const AttributeInfo& attrInfo)
     {
         // Hint for the pixel output format for all or one of the render targets
         if (attrInfo.m_attribute == "output_format")
@@ -75,7 +77,7 @@ namespace AZ::ShaderCompiler
             bool isIndexed = (attrInfo.m_argList.size() == 2 && holds_alternative<string>(attrInfo.m_argList[1]) && holds_alternative<ConstNumericVal>(attrInfo.m_argList[0]));
             if (!isDefault && !isIndexed)
             {
-                return;
+                return true;
             }
 
             OutputFormat hint = OutputFormat::FromStr(Trim(get<string>(attrInfo.m_argList[isDefault ? 0 : 1]), "\""));
@@ -93,7 +95,13 @@ namespace AZ::ShaderCompiler
                 }
             }
         }
+        else if (attrInfo.m_attribute == "pad_to")
+        {
+            m_padToAttributeMutator.ProcessPadToAttribute(attrInfo);
+            return false; //Do not store this attribute
+        }
         // The attribute has no special meaning to AZSLc, just pass it
+        return true;
     }
 
     void IntermediateRepresentation::RegisterTokenToNodeAssociation(ssize_t tokenId, antlr4::ParserRuleContext* node)
@@ -117,6 +125,8 @@ namespace AZ::ShaderCompiler
 
         RegisterRootConstantStruct(middleEndconfigration);
 
+        m_padToAttributeMutator.RunMutationsForPadToAttributes(middleEndconfigration);
+
         if (!middleEndconfigration.m_skipAlignmentValidation)
         {
             ValidateAlignmentIssueWhenScalarOrFloat2PrecededByMatrix(middleEndconfigration);
@@ -905,6 +915,45 @@ namespace AZ::ShaderCompiler
         throw AzslcIrException(IR_POTENTIAL_DX12_VS_VULKAN_ALIGNMENT_ERROR, errorMessage);
     }
 
+    IdAndKind& IntermediateRepresentation::GetCurrentScopeIdAndKind()
+    {
+        auto nameOfScope = m_scope.GetNameOfCurScope();
+        auto* idAndKindPtr = m_symbols.GetIdAndKindInfo(nameOfScope);
+        if (!idAndKindPtr)
+        {
+            throw std::logic_error("Internal error: current scope not registered");
+        }
+        return *idAndKindPtr;
+    }
+
+    IdentifierUID IntermediateRepresentation::GetLastMemberVariable(const IdentifierUID& uid)
+    {
+        auto kind = GetKind(uid);
+        ClassInfo* classInfo = nullptr; 
+        if (kind.IsOneOf(Kind::Struct, Kind::Class))
+        {
+            classInfo = GetSymbolSubAs<ClassInfo>(uid.GetName());
+        }
+        else if (kind.IsOneOf(Kind::ShaderResourceGroup))
+        {
+            auto srgInfo = GetSymbolSubAs<SRGInfo>(uid.GetName());
+            classInfo = &srgInfo->m_implicitStruct;
+        }
+
+        if (!classInfo)
+        {
+            return {};
+        }
+
+        const auto& memberList = classInfo->GetMemberFields();
+        if (memberList.empty())
+        {
+            return {};
+        }
+        return memberList[memberList.size() - 1];
+    }
+
+
     //////////////////////////////////////////////////////////////////////////
     // PreprocessorLineDirective overrides...
     const LineDirectiveInfo* IntermediateRepresentation::GetNearestPreprocessorLineDirective(size_t azslLineNumber) const
@@ -922,6 +971,17 @@ namespace AZ::ShaderCompiler
         }
         return nullptr;
     }
+
+    void IntermediateRepresentation::OverrideAzslcExceptionFileAndLine(size_t azslLineNumber) const
+    {
+        const LineDirectiveInfo* lineInfo = GetNearestPreprocessorLineDirective(azslLineNumber);
+        if (!lineInfo)
+        {
+            return;
+        }
+        AzslcException::s_currentSourceFileName = lineInfo->m_containingFilename;
+        AzslcException::s_sourceFileLineNumber = GetLineNumberInOriginalSourceFile(*lineInfo, azslLineNumber);
+    }
     ///////////////////////////////////////////////////////////////////////////
 
 }  // end of namespace AZ::ShaderCompiler

+ 21 - 1
src/AzslcIntermediateRepresentation.h

@@ -11,9 +11,11 @@
 #include "AzslcTokenToAst.h"
 #include "AzslcKindInfo.h"
 #include "PreprocessorLineDirectiveFinder.h"
+#include "PadToAttributeMutator.h"
 
 namespace AZ::ShaderCompiler
 {
+
     //! We limit the maximum number of render targets to 8, with indices in the range [0..7]
     static const uint32_t kMaxRenderTargets = 8;
 
@@ -41,6 +43,7 @@ namespace AZ::ShaderCompiler
                      }
             , m_lexer{ lexer }
             , m_sema{&m_symbols, &m_scope, lexer, this}
+            , m_padToAttributeMutator(*this)
         {
             // Default output format for all targets
             std::fill_n(m_metaData.m_outputFormatHint, kMaxRenderTargets, m_metaData.m_outputFormatHint[0]);
@@ -146,7 +149,9 @@ namespace AZ::ShaderCompiler
                                         azslParser::AttributeArgumentListContext* argList);
 
         //! called internally after a new attribute is registered
-        void ProcessAttributeSpecifier(const AttributeInfo& attrInfo);
+        //! Returns true if the attribute must be registered, otherwise
+        //! returns false.
+        bool ProcessAttributeSpecifier(const AttributeInfo& attrInfo);
 
         void RegisterTokenToNodeAssociation(ssize_t tokenId, antlr4::ParserRuleContext* node);
 
@@ -222,6 +227,7 @@ namespace AZ::ShaderCompiler
             return {};
         }
 
+        IdAndKind& GetCurrentScopeIdAndKind();
 
         // GFX-TODO: https://github.com/o3de/o3de-azslc/issues/9
         // Upgrade DXC and if the alignment issues are fixed when using
@@ -291,8 +297,19 @@ namespace AZ::ShaderCompiler
         //////////////////////////////////////////////////////////////////////////
         // PreprocessorLineDirective overrides...
         const LineDirectiveInfo* GetNearestPreprocessorLineDirective(size_t azslLineNumber) const override;
+        void OverrideAzslcExceptionFileAndLine(size_t azslLineNumber) const override;
         //////////////////////////////////////////////////////////////////////////
 
+        void ThrowAzslcIrException(uint32_t errorCode, size_t lineNumber, const string& message)
+        {
+            OverrideAzslcExceptionFileAndLine(lineNumber);
+            throw AzslcIrException(errorCode, message);
+        }
+
+        // Returns info for the last variable inside the struct or class named @structUid.
+        // If @structUid is not struct or class, then it returns nullptr.
+        IdentifierUID GetLastMemberVariable(const IdentifierUID& structUid);
+
         // the maps of all variables, functions, etc, from the source code (things with declarations and a name).
         SymbolAggregator      m_symbols;
         // stateful helper during parsing
@@ -309,6 +326,9 @@ namespace AZ::ShaderCompiler
         map<size_t, LineDirectiveInfo> m_lineMap;
 
         IRMetaData m_metaData;
+
+        // Helper to deal with the [[pad_to(N)]] attribute.
+        PadToAttributeMutator m_padToAttributeMutator;
     };
 
     string ToYaml(const TypeRefInfo& tref, const IntermediateRepresentation& ir, string_view indent);

+ 1 - 1
src/AzslcMain.cpp

@@ -23,7 +23,7 @@ namespace StdFs = std::filesystem;
 // For large features or milestones. Minor version allows for breaking changes. Existing tests can change.
 #define AZSLC_MINOR "7"
 // For small features or bug fixes. They cannot introduce breaking changes. Existing tests shouldn't change.
-#define AZSLC_REVISION "33" // Changing inlineConstant to rootConstant keyword work
+#define AZSLC_REVISION "34" // Add [[pad_to(N)]] language feature
 
 namespace AZ::ShaderCompiler
 {

+ 2 - 1
src/AzslcReflection.cpp

@@ -541,7 +541,8 @@ namespace AZ::ShaderCompiler
                     // If array is a structure
                     if (IsProductType(varClass))
                     {
-                        size = BuildUserDefinedMemberLayout(membersContainer, exportedType.m_typeId, options, layoutPacking, startAt = offset,
+                        startAt = offset;
+                        size = BuildUserDefinedMemberLayout(membersContainer, exportedType.m_typeId, options, layoutPacking, startAt,
                             (namePrefix.data() + shortName + strDimIndex + "."));
 
                         offset = Packing::PackNextChunk(layoutPacking, size, startAt);

+ 0 - 15
src/AzslcSemanticOrchestrator.cpp

@@ -966,21 +966,6 @@ namespace AZ::ShaderCompiler
         return funcInfo ? funcInfo->HasAnyDefaultParameterValue() : false;
     }
 
-    void SemanticOrchestrator::OverrideAzslcExceptionFileAndLine(size_t azslLineNumber) const
-    {
-        if (!m_preprocessorLineDirectiveFinder)
-        {
-            return;
-        }
-        const LineDirectiveInfo* lineInfo = m_preprocessorLineDirectiveFinder->GetNearestPreprocessorLineDirective(azslLineNumber);
-        if (!lineInfo)
-        {
-            return;
-        }
-        AzslcException::s_currentSourceFileName = lineInfo->m_containingFilename;
-        AzslcException::s_sourceFileLineNumber = m_preprocessorLineDirectiveFinder->GetLineNumberInOriginalSourceFile(*lineInfo, azslLineNumber);
-    }
-
     IdAndKind* SemanticOrchestrator::ResolveOverload(IdAndKind* maybeOverloadSet, azslParser::ArgumentListContext* argumentListCtx) const
     {
         IdAndKind* toReturn = maybeOverloadSet;

+ 10 - 11
src/AzslcSemanticOrchestrator.h

@@ -381,31 +381,30 @@ namespace AZ::ShaderCompiler
         //! queries whether a function has default parameters
         bool HasAnyDefaultParameterValue(const IdentifierUID& functionUid) const;
 
-        //! A helper method. Configures the exception system to report the original
-        //! source file and line location given the line number in the file that is being compiled.
-        void OverrideAzslcExceptionFileAndLine(size_t azslLineNumber) const;
-
-        //! Same as bove, but gets the @azslLineNumber from the @errorToken.
-        void OverrideAzslcExceptionFileAndLine(Token* errorToken) const { OverrideAzslcExceptionFileAndLine(errorToken->getLine()); }
-
         void ThrowAzslcOrchestratorException(uint32_t errorCode, optional<size_t> line, optional<size_t> column, const string& message) const
         {
-            if (line)
+            if (line && m_preprocessorLineDirectiveFinder)
             {
-                OverrideAzslcExceptionFileAndLine(line.value());
+                m_preprocessorLineDirectiveFinder->OverrideAzslcExceptionFileAndLine(line.value());
             }
             throw AzslcOrchestratorException(errorCode, line, column, message);
         }
 
         void ThrowAzslcOrchestratorException(uint32_t errorCode, Token* token, const string& message) const
         {
-            OverrideAzslcExceptionFileAndLine(token);
+            if (token && m_preprocessorLineDirectiveFinder)
+            {
+                m_preprocessorLineDirectiveFinder->OverrideAzslcExceptionFileAndLine(token->getLine());
+            }
             throw AzslcOrchestratorException(errorCode, token, message);
         }
 
         void ThrowRedeclarationAsDifferentKindInternal(string_view symbolName, Kind newKind, const KindInfo& kindInfo, size_t lineNumber) const
         {
-            OverrideAzslcExceptionFileAndLine(lineNumber);
+            if (lineNumber && m_preprocessorLineDirectiveFinder)
+            {
+                m_preprocessorLineDirectiveFinder->OverrideAzslcExceptionFileAndLine(lineNumber);
+            }
             ThrowRedeclarationAsDifferentKind(symbolName, newKind, kindInfo, lineNumber);
         }
 

+ 30 - 0
src/AzslcUtils.h

@@ -230,6 +230,36 @@ namespace AZ::ShaderCompiler
         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)
     {

+ 5 - 0
src/GenericUtils.h

@@ -509,6 +509,11 @@ namespace AZ
         }
         return result;
     }
+
+    inline bool IsPowerOfTwo(uint32_t value)
+    {
+        return (value > 0) && !(value & (value - 1));
+    }
 }
 
 #ifndef NDEBUG

+ 540 - 0
src/PadToAttributeMutator.cpp

@@ -0,0 +1,540 @@
+/*
+ * 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 "PadToAttributeMutator.h"
+#include "AzslcIntermediateRepresentation.h"
+
+namespace AZ::ShaderCompiler
+{
+    void PadToAttributeMutator::ProcessPadToAttribute(const AttributeInfo& attrInfo)
+    {
+        //The pad_to(N) attribute only accepts one input argument.
+        if (attrInfo.m_argList.size() != 1)
+        {
+            auto errorMsg = FormatString("The [[pad_to(N)]] attribute only accepts one argument of integral type. %zu arguments were given.", attrInfo.m_argList.size());
+            m_ir.ThrowAzslcIrException(IR_INVALID_PAD_TO_ARGUMENTS, attrInfo.m_lineNumber, errorMsg);
+        }
+        //Is the argument integral?
+        if (!holds_alternative<ConstNumericVal>(attrInfo.m_argList[0]))
+        {
+            string errorMsg("The [[pad_to(N)]] attribute only accepts one argument of integral type. A non integral argument was given.");
+            m_ir.ThrowAzslcIrException(IR_INVALID_PAD_TO_ARGUMENTS, attrInfo.m_lineNumber, errorMsg);
+        }
+        // Read the integral.
+        auto pad_to_value = ExtractValueAs<uint32_t>(get<ConstNumericVal>(attrInfo.m_argList[0]), uint32_t(0));
+        if (!pad_to_value)
+        {
+            string errorMsg("Failed to read input integral to [[pad_to(N)]].");
+            m_ir.ThrowAzslcIrException(IR_INVALID_PAD_TO_ARGUMENTS, attrInfo.m_lineNumber, errorMsg);
+        }
+        // Must be a multiple of 4.
+        static const uint32_t MultipleOf = 4;
+        if (pad_to_value & (MultipleOf-1))
+        {
+            auto errorMsg = FormatString("Invalid integral in [[pad_to(N)]]. %u is not a multiple of %u", pad_to_value, MultipleOf);
+            m_ir.ThrowAzslcIrException(IR_INVALID_PAD_TO_ARGUMENTS, attrInfo.m_lineNumber, errorMsg);
+        }
+
+        auto& [curScopeId, curScopeKindInfo] = m_ir.GetCurrentScopeIdAndKind();
+        if (curScopeKindInfo.IsKindOneOf(Kind::Struct, Kind::Class, Kind::ShaderResourceGroup))
+        {
+            // We need to get the variable declared before this attribute.
+            auto varUid = m_ir.GetLastMemberVariable(curScopeId);
+            if (varUid.IsEmpty())
+            {
+                auto errorMsg = FormatString("The [[pad_to(N)]] attribute must be added after a member variable."
+                                             " The current scope '%.*s' doesn't have a declared variable yet.",
+                                             static_cast<int>(curScopeId.GetName().size()), curScopeId.GetName().data());
+                m_ir.ThrowAzslcIrException(IR_INVALID_PAD_TO_LOCATION, attrInfo.m_lineNumber, errorMsg);
+            }
+            auto structItor = m_scopesToPad.find(curScopeId);
+            if (structItor == m_scopesToPad.end())
+            {
+                m_scopesToPad[curScopeId] = MapOfVarInfoUidToPadding();
+            }
+            MapOfVarInfoUidToPadding& varInfoUidToPadMap = m_scopesToPad[curScopeId];
+            auto varInfoItor = varInfoUidToPadMap.find(varUid);
+            if (varInfoItor != varInfoUidToPadMap.end())
+            {
+                // It appears that there are two consecutive [[pad_to(N)]] attributes. This is an error.
+                auto errorMsg = string("Two consecutive [[pad_to(N)]] attributes are not allowed inside 'struct'.");
+                m_ir.ThrowAzslcIrException(IR_INVALID_PAD_TO_LOCATION, attrInfo.m_lineNumber, errorMsg);
+            }
+            varInfoUidToPadMap[varUid] = pad_to_value;
+        }
+        else
+        {
+            auto errorMsg = FormatString("The [[pad_to(N)]] attribute is only supported inside  inside 'struct', 'class' or 'ShaderResourceGroup'."
+                                         " The current scope '%.*s' is not one of those scope types.",
+                static_cast<int>(curScopeId.GetName().size()), curScopeId.GetName().data());
+            m_ir.ThrowAzslcIrException(IR_INVALID_PAD_TO_LOCATION, attrInfo.m_lineNumber, errorMsg);
+        }
+    }
+
+    void PadToAttributeMutator::RunMutationsForPadToAttributes(const MiddleEndConfiguration& middleEndconfigration)
+    {
+        if (m_scopesToPad.empty())
+        {
+            return;
+        }
+
+        // Build sorted list from the keys in @m_scopesToPad so that the first 'structs' don't reference other structs that require padding.
+        // In other words, We must first pad the structs that don't reference other structs that need padding.
+        const auto sortedStructUids = GetSortedScopeUidList(m_scopesToPad);
+
+        for (const auto& scopeUid : sortedStructUids)
+        {
+            const auto& varInfoUidToPadMap = m_scopesToPad[scopeUid];
+            ClassInfo* classInfo = nullptr;
+            auto kind = m_ir.GetKind(scopeUid);
+            if (kind.IsOneOf(Kind::Struct, Kind::Class))
+            {
+                classInfo = m_ir.GetSymbolSubAs<ClassInfo>(scopeUid.GetName());
+            }
+            else if (kind.IsOneOf(Kind::ShaderResourceGroup))
+            {
+                auto srgInfo = m_ir.GetSymbolSubAs<SRGInfo>(scopeUid.GetName());
+                classInfo = &srgInfo->m_implicitStruct;
+            }
+            
+            if (!classInfo)
+            {
+                auto errorMsg = FormatString("Error during struct padding: couldn't find ClassInfo for scope %.*s", scopeUid.GetName().size(), scopeUid.GetName().data());
+                throw std::logic_error(errorMsg);
+            }
+            InsertScopePaddings(classInfo, scopeUid, varInfoUidToPadMap, middleEndconfigration);
+        }
+    }
+
+    vector<IdentifierUID> PadToAttributeMutator::GetSortedScopeUidList(const MapOfScopeUidToPaddingMap& scopesToPad) const
+    {
+        vector<IdentifierUID> sortedList;
+
+        unordered_set<IdentifierUID> visitedStructs;
+        unordered_set<IdentifierUID> unvisitedStructs;
+        for (const auto &item : scopesToPad)
+        {
+            unvisitedStructs.insert(item.first);
+        }
+        
+        while (!unvisitedStructs.empty())
+        {
+            const IdentifierUID unvisitedStructUid = *unvisitedStructs.begin();
+            ScopeUidSortVisitFunction(unvisitedStructUid, visitedStructs, sortedList);
+            unvisitedStructs.erase(unvisitedStructUid);
+        }
+
+        return sortedList;
+    }
+
+
+    void PadToAttributeMutator::ScopeUidSortVisitFunction(const IdentifierUID& scopeUid, unordered_set<IdentifierUID>& visitedScopes, vector<IdentifierUID>& sortedList) const
+    {
+        if (visitedScopes.count(scopeUid))
+        {
+            return;
+        }
+        
+        ClassInfo* classInfo = nullptr;
+        auto kind = m_ir.GetKind(scopeUid);
+        if (kind.IsOneOf(Kind::Struct, Kind::Class))
+        {
+            classInfo = m_ir.GetSymbolSubAs<ClassInfo>(scopeUid.GetName());
+        }
+        else if (kind.IsOneOf(Kind::ShaderResourceGroup))
+        {
+            auto srgInfo = m_ir.GetSymbolSubAs<SRGInfo>(scopeUid.GetName());
+            classInfo = &srgInfo->m_implicitStruct;
+        }
+
+        const auto listOfPairs = GetVariablesOfScopeTypeThatRequirePadding(classInfo);
+        for (const auto& [typeUid, varUid] : listOfPairs)
+        {
+            ScopeUidSortVisitFunction(typeUid, visitedScopes, sortedList);
+        }
+        
+        visitedScopes.insert(scopeUid);
+        sortedList.push_back(scopeUid);
+    }
+
+
+    vector<pair<IdentifierUID, IdentifierUID>> PadToAttributeMutator::GetVariablesOfScopeTypeThatRequirePadding(const ClassInfo* classInfo) const
+    {
+        vector<pair<IdentifierUID, IdentifierUID>> retList;
+        const auto& memberFields = classInfo->GetMemberFields();
+        for (const auto &memberUid : memberFields)
+        {
+            const auto* varInfoPtr = m_ir.GetSymbolSubAs<VarInfo>(memberUid.m_name);
+            if (!varInfoPtr)
+            {
+                continue;
+            }
+            const auto& typeUid = varInfoPtr->GetTypeId();
+            const auto kind = m_ir.GetKind(typeUid);
+            if (!kind.IsOneOf(Kind::Struct, Kind::Class)) // No need to check for SRG types, as SRGs can not be variables.
+            {
+                continue;
+            }
+            if (m_scopesToPad.find(typeUid) == m_scopesToPad.end())
+            {
+                // It's of struct type, but doesn't require padding.
+                continue;
+            }
+            retList.emplace_back(typeUid, memberUid);
+        }
+        return retList;
+    }
+
+    void PadToAttributeMutator::InsertScopePaddings(ClassInfo* classInfo,
+                                                  const IdentifierUID& scopeUid,
+                                                  const MapOfVarInfoUidToPadding& varInfoUidToPadMap,
+                                                  const MiddleEndConfiguration& middleEndconfigration)
+    {
+        uint32_t nextMemberOffset = 0;
+        auto& memberFields = classInfo->GetMemberFields();
+        for (size_t idx = 0; idx < memberFields.size(); idx++)
+        {
+            // Calculate current offset & size.
+            const auto& varUid = memberFields[idx];
+            CalculateMemberLayout(varUid, false, middleEndconfigration.m_isRowMajor, middleEndconfigration.m_packDataBuffers, nextMemberOffset);
+
+            // Nothing else to do, if this variable doesn't need padding.
+            const auto varItor = varInfoUidToPadMap.find(varUid);
+            if (varItor == varInfoUidToPadMap.end())
+            {
+                continue;
+            }
+
+            const uint32_t padToBoundary = varItor->second;
+            uint32_t bytesToAdd = 0;
+            if (padToBoundary < nextMemberOffset)
+            {
+                // We will only AlignUp if padToBoundary is a power of two.
+                if (!IsPowerOfTwo(padToBoundary))
+                {
+                    //Runtime error.
+                    const string errorMsg = FormatString("Offset %u after Member variable %.*s of struct %.*s "
+                                                         "is bigger than requested boundary = [[pad_to(%u)]], and this case requires a power of two boundary.",
+                                                          nextMemberOffset,
+                                                          static_cast<int>(varUid.m_name.size()), varUid.m_name.data(),
+                                                          static_cast<int>(scopeUid.m_name.size()), scopeUid.m_name.data(),
+                                                          padToBoundary);
+                    const auto * varInfoPtr= m_ir.GetSymbolSubAs<VarInfo>(varUid.m_name);
+                    m_ir.ThrowAzslcIrException(IR_PAD_TO_CASE_REQUIRES_POWER_OF_TWO, varInfoPtr->GetOriginalLineNumber(), errorMsg);
+                }
+                const uint32_t alignedOffset = Packing::AlignUp(nextMemberOffset, padToBoundary);
+                bytesToAdd = alignedOffset - nextMemberOffset;
+            }
+            else
+            {
+                bytesToAdd = padToBoundary - nextMemberOffset;
+            }
+
+            if (!bytesToAdd)
+            {
+                // Nothing to do.
+                continue;
+            }
+
+            idx += InsertPaddingVariables(classInfo, scopeUid, idx+1, nextMemberOffset, bytesToAdd);
+            nextMemberOffset += bytesToAdd;
+        }
+    }
+
+    uint32_t PadToAttributeMutator::CalculateMemberLayout(const IdentifierUID& memberId,
+                                                       const bool isArrayItr,
+                                                       const bool emitRowMajor,
+                                                       const AZ::ShaderCompiler::Packing::Layout layoutPacking,
+                                                       uint32_t& offset) const
+    {
+        const auto* varInfoPtr = m_ir.GetSymbolSubAs<VarInfo>(memberId.m_name);
+        uint32_t size = 0;
+
+        if (varInfoPtr)
+        {
+            const auto& varInfo = *varInfoPtr;
+
+            // View types should only be called from GetViewStride until we decide to support them as struct constants
+            assert(!IsChameleon(varInfo.GetTypeClass()));
+
+            auto exportedType = varInfo.m_typeInfoExt.m_coreType;
+
+            if (!exportedType.IsPackable())
+            {
+                throw std::logic_error{"reflection error: unpackable type ("
+                    + exportedType.m_typeId.m_name
+                    + ") in layout member "
+                    + memberId.m_name};
+            }
+            TypeClass varClass = exportedType.m_typeClass;
+            bool isPrefedined  = IsPredefinedType(varClass); 
+
+            size = varInfo.m_typeInfoExt.GetTotalSize(layoutPacking, emitRowMajor);
+            auto startAt = offset;
+
+            // Alignment start
+            if (exportedType.m_arithmeticInfo.IsMatrix() || exportedType.m_arithmeticInfo.IsVector())
+            {
+                const auto rows = exportedType.m_arithmeticInfo.m_rows;
+                const auto cols = exportedType.m_arithmeticInfo.m_cols;
+                const auto packAlignment = exportedType.m_arithmeticInfo.IsMatrix() ? Packing::Alignment::asMatrixStart : Packing::Alignment::asVectorStart;
+                startAt = offset = Packing::AlignOffset(layoutPacking, offset, packAlignment, rows, cols);
+            }
+
+            uint32_t totalArraySize = 1;
+            ArrayDimensions listOfArrayDim = varInfo.m_typeInfoExt.GetDimensions();
+            std::reverse(listOfArrayDim.m_dimensions.begin(), listOfArrayDim.m_dimensions.end());
+            for (const auto dim : varInfo.m_typeInfoExt.GetDimensions())
+            {
+                totalArraySize *= dim;
+            }
+
+            if (varInfo.m_typeInfoExt.IsArray() && !isArrayItr)
+            {
+                startAt = offset = Packing::AlignOffset(layoutPacking, offset, Packing::Alignment::asArrayStart, 0, 0);
+                uint32_t arrayOffset = startAt;
+                for (uint32_t i = 0; i < totalArraySize; i++)
+                {
+                    if (!m_ir.GetIdAndKindInfo(varInfo.GetTypeId().m_name))
+                    {
+                        continue;
+                    }
+
+                    // If array is a structure
+                    if (IsProductType(varClass))
+                    {
+                        startAt = offset;
+                        size = CalculateUserDefinedMemberLayout(exportedType.m_typeId, emitRowMajor, layoutPacking, startAt);
+
+                        offset = Packing::PackNextChunk(layoutPacking, size, startAt);
+
+                        // Add packing into array
+                        size = Packing::PackIntoArray(layoutPacking, size, varInfo.m_typeInfoExt.GetDimensions());
+                    }
+                    else
+                    {
+                        // Alignment start
+                        startAt = offset = Packing::AlignOffset(layoutPacking, offset, Packing::Alignment::asArrayStart, 0, 0);
+
+                        // We want to calculate the offset for each array element
+                        uint32_t tempOffset = startAt;
+
+                        CalculateMemberLayout(memberId, true, emitRowMajor, layoutPacking, tempOffset);
+
+                        // Alignment end
+                        tempOffset = Packing::AlignOffset(layoutPacking, tempOffset, Packing::Alignment::asArrayEnd, 0, 0);
+                        size = tempOffset - startAt;
+
+                        offset = Packing::PackNextChunk(layoutPacking, size, startAt);
+
+                        // Add packing into array
+                        size = Packing::PackIntoArray(layoutPacking, size, varInfo.m_typeInfoExt.GetDimensions());
+                    }
+                }
+                startAt = arrayOffset;
+            }
+            else if (IsProductType(varClass))
+            {
+                size = CalculateUserDefinedMemberLayout(exportedType.m_typeId, emitRowMajor, layoutPacking, startAt);
+
+                // Add packing into array
+                size = Packing::PackIntoArray(layoutPacking, size, varInfo.m_typeInfoExt.GetDimensions());
+            }
+            else if (varInfo.m_typeInfoExt.IsArray())
+            {
+                // Get the size of one element from total size
+                size = varInfo.m_typeInfoExt.GetSingleElementSize(layoutPacking, emitRowMajor);
+            }
+            else if (varInfo.GetTypeClass() == TypeClass::Enum)
+            {
+                auto* asClassInfo = m_ir.GetSymbolSubAs<ClassInfo>(varInfo.GetTypeId().GetName());
+                size = asClassInfo->Get<EnumerationInfo>()->m_underlyingType.m_arithmeticInfo.GetBaseSize();
+            }
+
+            offset = Packing::PackNextChunk(layoutPacking, size, startAt);
+
+            // Alignment end
+            if (exportedType.m_arithmeticInfo.IsMatrix() || exportedType.m_arithmeticInfo.IsVector())
+            {
+                const auto rows = exportedType.m_arithmeticInfo.m_rows;
+                const auto cols = exportedType.m_arithmeticInfo.m_cols;
+                const auto packAlignment = exportedType.m_arithmeticInfo.IsMatrix() ? Packing::Alignment::asMatrixEnd : Packing::Alignment::asVectorEnd;
+                offset = Packing::AlignOffset(layoutPacking, offset, packAlignment, rows, cols);
+            }
+
+            if (varInfo.m_typeInfoExt.IsArray())
+            {
+                offset = Packing::AlignOffset(layoutPacking, offset, Packing::Alignment::asArrayEnd, 0, 0);
+            }
+
+            size = offset - startAt;
+        }
+
+        return size;
+    }
+
+
+    uint32_t PadToAttributeMutator::CalculateUserDefinedMemberLayout(
+                                                          const IdentifierUID& exportedTypeId,
+                                                          const bool emitRowMajors,
+                                                          const AZ::ShaderCompiler::Packing::Layout layoutPacking,
+                                                          uint32_t& startAt) const
+    {
+        // Alignment start
+        uint32_t tempOffset = startAt = Packing::AlignOffset(layoutPacking, startAt, Packing::Alignment::asStructStart, 0, 0);
+
+        uint32_t largestMemberSize = 0;
+
+        const auto* classInfo = m_ir.GetSymbolSubAs<ClassInfo>(exportedTypeId.m_name);
+        for (const auto& memberField : classInfo->GetMemberFields())
+        {
+            const auto currentStride = tempOffset;
+            CalculateMemberLayout(memberField, false, emitRowMajors, GetExtendedLayout(layoutPacking), tempOffset);
+            largestMemberSize = std::max(largestMemberSize, tempOffset - currentStride);
+        }
+
+        tempOffset = Packing::AlignStructToLargestMember(layoutPacking, tempOffset, largestMemberSize);
+
+        // Alignment end
+        tempOffset = Packing::AlignOffset(layoutPacking, tempOffset, Packing::Alignment::asStructEnd, 0, 0);
+
+        // Total size equals the end offset less the starting address
+        return tempOffset - startAt;   
+    }
+
+
+    size_t PadToAttributeMutator::InsertPaddingVariables(ClassInfo* classInfo, const IdentifierUID& scopeUid,
+                                                      size_t insertionIndex, uint32_t startingOffset, uint32_t numBytesToAdd)
+    {
+        auto getFloatTypeNameOfSize = +[](uint32_t sizeInBytes) -> const char *
+        {
+            static const char * floatNames[4] = {
+                "float", "float2", "float3", "float4"
+            };
+            const uint32_t idx = (sizeInBytes >> 2) - 1;
+            return floatNames[idx];
+        };
+
+        auto createVariableInSymbolTable = [&](QualifiedNameView parentName, const string& typeName, UnqualifiedName varName, uint32_t itemsCount = 0) -> IdentifierUID
+        {
+            QualifiedName dummySymbolFieldName{ JoinPath(parentName, varName) };
+
+            // Add the dummy field to the symbol table.
+            auto& [newVarUid, newVarKind] = m_ir.m_symbols.AddIdentifier(dummySymbolFieldName, Kind::Variable);
+            // Fill up the data.
+            VarInfo newVarInfo;
+            newVarInfo.m_declNode = nullptr;
+            newVarInfo.m_isPublic = false;
+            ExtractedTypeExt padType = { UnqualifiedNameView(typeName), nullptr };
+            if (itemsCount < 1)
+            {
+                newVarInfo.m_typeInfoExt = ExtendedTypeInfo{ m_ir.m_sema.CreateTypeRefInfo(padType),
+                             {}, {}, {}, Packing::MatrixMajor::Default };
+            }
+            else
+            {
+                newVarInfo.m_typeInfoExt = ExtendedTypeInfo{ m_ir.m_sema.CreateTypeRefInfo(padType),
+                    {}, {{itemsCount}}, {}, Packing::MatrixMajor::Default };
+            }
+            newVarKind.GetSubRefAs<VarInfo>() = newVarInfo;
+            return newVarUid;
+        };
+
+        // The key idea is to add, at most, three variables. They will be added depending on keeping a 16-byte alignment from @startingOffset
+        // 1- The first variable will be added if @startingOffset is not 16-bytes aligned. It will be a float, float2 or float3.
+        // 2- If more bytes are still needed, then We'll add ONE float4[N] array, Until (N * 16) bytes fit within the bytes that are left to add.
+        // 3- Finally, if there are more remaining bytes to the be added, a third float, float2 or float3 will be added.
+
+        auto& memberFields = classInfo->GetMemberFields();
+        IdentifierUID insertBeforeThisUid;
+        if (insertionIndex <= memberFields.size() - 1)
+        {
+            insertBeforeThisUid = memberFields[insertionIndex];
+        }
+        size_t numAddedVariables = 0;
+
+        // 1st variable.
+        // This is why the 1st variable is needed:
+        // For non-ConstantBuffer packing the float4 is not automatically aligned to 16 bytes.
+        // Example:
+        // struct MyStructA
+        // {
+        //     float m_data;
+        //     float4 m_arr[2];
+        // };
+        // For ConstantBuffer case you'll get these offsets:
+        //     float m_data;                     ; Offset:    0
+        //     float4 m_arr[2];                  ; Offset:   16
+        // For StructuredBuffer case you'll get:
+        //     float m_data;                     ; Offset:    0
+        //     float4 m_arr[2];                  ; Offset:    4
+        {
+            const auto alignedOffset = Packing::AlignUp(startingOffset, 16);
+            const auto deltaBytes = alignedOffset - startingOffset;
+            if (deltaBytes < numBytesToAdd)
+            {
+                string typeName = getFloatTypeNameOfSize(deltaBytes);
+                auto variableName = FormatString("__pad_at%u", startingOffset);
+                IdentifierUID newVarUid = createVariableInSymbolTable(scopeUid.GetName(), typeName, UnqualifiedName{variableName});
+                if (insertBeforeThisUid.IsEmpty())
+                {
+                    classInfo->PushMember(newVarUid, Kind::Variable);
+                }
+                else
+                {
+                    classInfo->InsertBefore(newVarUid, Kind::Variable, insertBeforeThisUid);
+                }
+                numAddedVariables++;
+                numBytesToAdd -= deltaBytes;
+                startingOffset = alignedOffset;
+            }
+        }
+
+        // 2nd variable. The Array of 'float4'
+        {
+            const auto numFloat4s = numBytesToAdd >> 4;
+            if (numFloat4s)
+            {
+                auto variableName = FormatString("__pad_at%u", startingOffset);
+                IdentifierUID newVarUid = createVariableInSymbolTable(scopeUid.GetName(), "float4", UnqualifiedName{variableName}, numFloat4s);
+                if (insertBeforeThisUid.IsEmpty())
+                {
+                    classInfo->PushMember(newVarUid, Kind::Variable);
+                }
+                else
+                {
+                    classInfo->InsertBefore(newVarUid, Kind::Variable, insertBeforeThisUid);
+                }
+                numAddedVariables++;
+                numBytesToAdd -= (numFloat4s << 4);
+                startingOffset += (numFloat4s << 4);
+            }
+        }
+
+        // 3rd variable. The remainder
+        if (numBytesToAdd > 0)
+        {
+            auto variableName = FormatString("__pad_at%u", startingOffset);
+            string typeName = getFloatTypeNameOfSize(numBytesToAdd);
+            IdentifierUID newVarUid = createVariableInSymbolTable(scopeUid.GetName(), typeName, UnqualifiedName{variableName});
+            if (insertBeforeThisUid.IsEmpty())
+            {
+                classInfo->PushMember(newVarUid, Kind::Variable);
+            }
+            else
+            {
+                classInfo->InsertBefore(newVarUid, Kind::Variable, insertBeforeThisUid);
+            }
+            numAddedVariables++;
+        }
+
+        return numAddedVariables;
+    }
+
+} //namespace AZ::ShaderCompiler

+ 114 - 0
src/PadToAttributeMutator.h

@@ -0,0 +1,114 @@
+/*
+ * 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 "GenericUtils.h"
+#include "AzslcKindInfo.h"
+
+namespace AZ::ShaderCompiler
+{
+    struct IntermediateRepresentation;
+
+    //! This is a companion class to AZ::ShaderCompiler::IntermediateRepresentation.
+    //! It consolidates all functions and algoritms required to process the
+    //! [[pad_to(N)]]
+    //! attribute.
+    //! The [[pad_to(N)]] attribute is only allowed in scopes of type 'struct', 'class' and 'ShaderResourceGroup', and
+    //! only valid AFTER a member variable declaration.
+    //! 'N' must be a multiple of 4.
+    //! [[pad_to(N)]] inserts dummy variables until the next variable in the scope
+    //! starts at an offset aligned to N.
+    class PadToAttributeMutator final
+    {
+    public:
+        PadToAttributeMutator() = delete;
+        explicit PadToAttributeMutator(IntermediateRepresentation& ir) : m_ir(ir) {}
+        ~PadToAttributeMutator() = default;
+
+        //! Called each time a [[pad_to(N)]] line is found when the input file is being parsed.
+        //! Throws an exception in case of error.
+        void ProcessPadToAttribute(const AttributeInfo& attrInfo);
+
+        // This function is the main entry point to do code mutation related with the [[pad_to(N)]] attribute.
+        // It is ran during the MiddleEnd() that will mutate
+        // the symbols table (@m_symbols) by inserting  padding variables
+        // per the data recorded in @m_scopesToPad.
+        // If there are no [[pad_to(N)]] attributes, this function is a no-op.
+        void RunMutationsForPadToAttributes(const MiddleEndConfiguration& middleEndconfigration);
+
+    private:
+
+        using MapOfVarInfoUidToPadding = unordered_map<IdentifierUID, uint32_t>;
+        using MapOfScopeUidToPaddingMap = unordered_map<IdentifierUID, MapOfVarInfoUidToPadding>;
+
+        ///////////////////////////////////////////////////////////////////////
+        // Functions used for sorting START
+
+        // Returns a vector<IdentifierUID> with the sorted scope IdentifierUID from @scopesToPad.
+        // This function runs a classical depth-first search topological sort.
+        // The idea is that the IdentifierUID at the end of the returned vector represent scopes that
+        // need padding but they depend on scopes at the beginnning of the vector to be padded first.
+        vector<IdentifierUID> GetSortedScopeUidList(const MapOfScopeUidToPaddingMap& scopesToPad) const;
+
+        // Helper Recursive function for depth-search topological sorting of the Scope Uids that require padding.
+        void ScopeUidSortVisitFunction(const IdentifierUID& scopeUid, unordered_set<IdentifierUID>& visitedScopes, vector<IdentifierUID>& sortedList) const;
+
+        // Helper function used during ScopeUidSortVisitFunction()
+        // pair.first is the struct type IdentifierUID.
+        // pair.second is the variable IdentifierUID.
+        vector<pair<IdentifierUID, IdentifierUID>> GetVariablesOfScopeTypeThatRequirePadding(const ClassInfo* classInfo) const;
+
+        // Functions used for sorting END
+        ///////////////////////////////////////////////////////////////////////
+
+        // Inserts the requested paddings to the scope named @scopeUid.
+        // @classInfo is the ClassInfo data for @scopeUid.
+        void InsertScopePaddings(ClassInfo* classInfo,
+                                  const IdentifierUID& scopeUid, const MapOfVarInfoUidToPadding& varInfoUidToPadMap,
+                                  const MiddleEndConfiguration& middleEndconfigration);
+
+        // Recursive function that returns the size of a variable @memberId.
+        // Additionally in the in-out parameter @offset it accepts the starting offset
+        // of the variable and updates it to what should be the offset of the next variable.
+        uint32_t CalculateMemberLayout(const IdentifierUID& memberId,
+                                       const bool isArrayItr,
+                                       const bool emitRowMajor,
+                                       const AZ::ShaderCompiler::Packing::Layout layoutPacking,
+                                       uint32_t& offset /*in-out*/) const;
+
+        // Recursive function, companion of CalculateMemberLayout() that is called
+        // when a variable is of type struct, class or interface.
+        // Returns the whole size of the struct, class or interface.
+        uint32_t CalculateUserDefinedMemberLayout(const IdentifierUID& exportedTypeId,
+                                                  const bool emitRowMajor,
+                                                  const AZ::ShaderCompiler::Packing::Layout layoutPacking,
+                                                  uint32_t& startAt /*in-out*/) const;
+
+        // Adds the minimum amount of variables to @classInfo until the struct/class/SRG grows in size by @numBytesToAdd
+        // @param classInfo The ClassInfo pointer that belongs to @scopeUid.
+        // @param scopeUid IdentifierUID of the struct/class/SRG
+        // @param insertionIndex Index within ClassInfo::m_memberFields where the new variables should be inserted.
+        // @param startingOffset Used as alignment reference for the bytes that will be added.
+        // @param numBytesToAdd Will add as many variables as necessary until this amount of bytes are
+        //        appended to the ClassInfo.
+        // @returns The number of unique VarInfo that were added to ClassInfo::m_memberFields
+        size_t InsertPaddingVariables(ClassInfo* classInfo, const IdentifierUID& scopeUid, size_t insertionIndex, uint32_t startingOffset, uint32_t numBytesToAdd);
+
+        // This class is an extension of the IntermediateRepresentation.
+        IntermediateRepresentation& m_ir;
+
+        // This map is populated during input file parsing.
+        // The Key of this map is the UID of a struct or class.
+        // The Value is another Map that represents the variables within the struct
+        // that need to be padded to a specific boundary.
+        // The information stored in this map will be used during MutateStructs()
+        // to insert padding variables that will guarantee alignment boundaries as requested
+        // by the usage of [[pad_to(N)]] attributes.
+        MapOfScopeUidToPaddingMap m_scopesToPad;
+    };
+} // namespace AZ::ShaderCompiler

+ 2 - 0
src/PreprocessorLineDirectiveFinder.h

@@ -40,5 +40,7 @@ namespace AZ::ShaderCompiler
             const size_t absoluteLineNumberInIncludedFile = lineInfo.m_forcedLineNumber + relativeLineNumber - 1;
             return absoluteLineNumberInIncludedFile;
         }
+
+        virtual void OverrideAzslcExceptionFileAndLine(size_t azslLineNumber) const = 0;
     };
 }

+ 6 - 10
src/Texture2DMSto2DCodeMutator.cpp

@@ -1,14 +1,10 @@
 /*
-* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
-* its licensors.
-*
-* For complete copyright and license terms please see the LICENSE at the root of this
-* distribution (the "License"). All use of this software is governed by the License,
-* or, if provided, by the license below or the license accompanying this file. Do not
-* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-*
-*/
+ * 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 "Texture2DMSto2DCodeMutator.h"
 

+ 3 - 0
src/azslParser.g4

@@ -65,6 +65,7 @@ classMemberDeclaration:
     |   attributedFunctionDeclaration
     |   typeAliasingDefinitionStatement  // AZSL specificity
     |   anyStructuredTypeDefinitionStatement   // Amazon extension (DXC supports it)
+    |   attributeSpecifierAny // AZSL+. Allows [[pad_to(N)]].
 ;
 
 structDefinitionStatement:
@@ -82,6 +83,7 @@ structMemberDeclaration:
     |   attributedFunctionDeclaration //AZSL+, forbidden, but allows us to provide better error message.
     |   anyStructuredTypeDefinitionStatement  // AZSL+
     |   typeAliasingDefinitionStatement // AZSL+
+    |   attributeSpecifierAny // AZSL+. Allows [[pad_to(N)]].
 ;
 
 anyStructuredTypeDefinitionStatement:
@@ -878,6 +880,7 @@ srgMemberDeclaration:
     |   variableDeclarationStatement
     |   enumDefinitionStatement
     |   typeAliasingDefinitionStatement
+    |   attributeSpecifierAny // Allows [[pad_to(N)]].
 ;
 
 srgSemantic:

File diff suppressed because it is too large
+ 189 - 167
src/generated/azslParser.cpp


+ 3 - 0
src/generated/azslParser.h

@@ -457,6 +457,7 @@ public:
     AttributedFunctionDeclarationContext *attributedFunctionDeclaration();
     TypeAliasingDefinitionStatementContext *typeAliasingDefinitionStatement();
     AnyStructuredTypeDefinitionStatementContext *anyStructuredTypeDefinitionStatement();
+    AttributeSpecifierAnyContext *attributeSpecifierAny();
 
     virtual void enterRule(antlr4::tree::ParseTreeListener *listener) override;
     virtual void exitRule(antlr4::tree::ParseTreeListener *listener) override;
@@ -507,6 +508,7 @@ public:
     AttributedFunctionDeclarationContext *attributedFunctionDeclaration();
     AnyStructuredTypeDefinitionStatementContext *anyStructuredTypeDefinitionStatement();
     TypeAliasingDefinitionStatementContext *typeAliasingDefinitionStatement();
+    AttributeSpecifierAnyContext *attributeSpecifierAny();
 
     virtual void enterRule(antlr4::tree::ParseTreeListener *listener) override;
     virtual void exitRule(antlr4::tree::ParseTreeListener *listener) override;
@@ -2786,6 +2788,7 @@ public:
     VariableDeclarationStatementContext *variableDeclarationStatement();
     EnumDefinitionStatementContext *enumDefinitionStatement();
     TypeAliasingDefinitionStatementContext *typeAliasingDefinitionStatement();
+    AttributeSpecifierAnyContext *attributeSpecifierAny();
 
     virtual void enterRule(antlr4::tree::ParseTreeListener *listener) override;
     virtual void exitRule(antlr4::tree::ParseTreeListener *listener) override;

+ 87 - 0
tests/Advanced/pad-to-attribute-validation.py

@@ -0,0 +1,87 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+"""
+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
+"""
+import sys
+import os
+sys.path.append("..")
+from clr import *
+import testfuncs
+
+'''
+Validates the functionality of the [[pad_to(N)]] attribute for struct, class and SRGs.
+'''
+
+def check_StructuredBuffer_Vs_ConstantBuffer_Padding(thefile, compilerPath, silent, expectedSize):
+    # Compile the shader with --srg and check that the final size of the struct
+    # is 256 for both the StructureBuffer<MyStruct> DemoSrg::m_mySB, and MyStruct DemoSrg::m_myStruct
+    j, ok = testfuncs.buildAndGetJson(thefile, compilerPath, silent, ["--srg"])
+    if ok:
+        if not silent: print (fg.CYAN+ style.BRIGHT+ "checkPadding: Verifying struct sizes..."+ style.RESET_ALL)
+
+        predicates = []
+        predicates.append(lambda expectedSize=expectedSize: j["ShaderResourceGroups"][0]["inputsForBufferViews"][0]["stride"] == expectedSize)
+        predicates.append(lambda: j["ShaderResourceGroups"][0]["inputsForBufferViews"][0]["type"] == "StructuredBuffer<MyStruct>")
+
+        predicates.append(lambda expectedSize=expectedSize: j["ShaderResourceGroups"][0]["inputsForSRGConstants"][27]["constantByteSize"] == expectedSize)
+        predicates.append(lambda: j["ShaderResourceGroups"][0]["inputsForSRGConstants"][27]["typeName"] == "/MyStruct")
+
+        ok = testfuncs.verifyAllPredicates(predicates, j, silent)
+        if ok and not silent:
+            print (style.BRIGHT+ "OK! "+ str(len(predicates)) + " checkPadding: All sizes were the same." + style.RESET_ALL)
+    return ok
+
+
+def check_SRG_Padding(thefile, compilerPath, silent, expectedSize):
+    j, ok = testfuncs.buildAndGetJson(thefile, compilerPath, silent, ["--srg"])
+    if ok:
+        if not silent: print (fg.CYAN+ style.BRIGHT+ "check_SRG_Padding: Verifying SRG sizes..."+ style.RESET_ALL)
+
+        #The offset + size of the last variable in each SRG must match the value of @expectedSize.
+        srg1LastVariableOffset = j["ShaderResourceGroups"][0]["inputsForSRGConstants"][-1]["constantByteOffset"]
+        srg1LastVariableSize= j["ShaderResourceGroups"][0]["inputsForSRGConstants"][-1]["constantByteSize"]
+        srg1Size = srg1LastVariableOffset + srg1LastVariableSize
+
+        srg2LastVariableOffset = j["ShaderResourceGroups"][1]["inputsForSRGConstants"][-1]["constantByteOffset"]
+        srg2LastVariableSize= j["ShaderResourceGroups"][1]["inputsForSRGConstants"][-1]["constantByteSize"]
+        srg2Size = srg2LastVariableOffset + srg2LastVariableSize
+
+        ok = (srg1Size == srg2Size) and (srg1Size == expectedSize)
+        if not ok and not silent:
+            errorMsg = f"Was expecting both SRG sizes to be {expectedSize}, instead got SRG1 size={srg1Size} and SRG2 size={srg2Size}"
+            print (fg.RED + "FAIL (" + errorMsg + "):" + style.RESET_ALL)
+
+        if ok and not silent:
+            print (style.BRIGHT+ "OK! check_SRG_Padding: All sizes were the same." + style.RESET_ALL)
+    return ok
+
+
+result = 0  # to define for sub-tests
+resultFailed = 0
+def doTests(compiler, silent, azdxcpath):
+    global result
+    global resultFailed
+
+    # Working directory should have been set to this script's directory by the calling parent
+    # You can get it once doTests() is called, but not during initialization of the module,
+    #  because at that time it will still be set to the working directory of the calling script
+    workDir = os.getcwd()
+
+    if not silent: print ("testing [[pad_to(256)]] attribute...")
+    if check_StructuredBuffer_Vs_ConstantBuffer_Padding(os.path.join(workDir, "struct-pad-to-256.azsl"), compiler, silent, 256): result += 1
+    else: resultFailed += 1
+
+    if not silent: print ("testing [[pad_to(252)]] attribute...")
+    if check_StructuredBuffer_Vs_ConstantBuffer_Padding(os.path.join(workDir, "struct-pad-to-252.azsl"), compiler, silent, 252): result += 1
+    else: resultFailed += 1
+
+    if not silent: print ("testing [[pad_to(N)]] for SRGs...")
+    if check_SRG_Padding(os.path.join(workDir, "srg-pad-to-256.azsl"), compiler, silent, 256): result += 1
+    else: resultFailed += 1
+
+if __name__ == "__main__":
+    print ("please call from testapp.py")

+ 68 - 0
tests/Advanced/srg-pad-to-256.azsl

@@ -0,0 +1,68 @@
+ShaderResourceGroupSemantic slot1
+{
+    FrequencyId = 1;
+};
+
+ShaderResourceGroupSemantic slot2
+{
+    FrequencyId = 2;
+};
+
+struct MyStructA
+{
+    float m_data;
+    [[pad_to(16)]]
+};
+
+struct MyStructB
+{
+    int m_data;
+    [[pad_to(16)]]
+    MyStructA m_a;
+    float2 m_b;
+    [[pad_to(64)]]
+};
+
+class MyClassC
+{
+    MyStructA m_a;
+    MyStructB m_b;
+    float m_color_red;
+    [[pad_to(16)]]
+    uint m_uint;
+    [[pad_to(256)]]
+};
+
+ShaderResourceGroup DemoSrg : slot1
+{
+    MyClassC m_myStruct;
+
+    float GetRed()
+    {
+        return m_myStruct.m_color_red;
+    }
+};
+
+ShaderResourceGroup DemoSrg2 : slot2
+{
+    MyStructA m_a;
+    MyStructB m_b;
+    float m_color_red;
+    [[pad_to(16)]]
+    uint m_uint;
+    [[pad_to(256)]]
+
+    float4 GetColor()
+    {
+        return float4(m_color_red , m_b.m_b.x, m_b.m_b.y, m_a.m_data);
+    }
+};
+
+float4 PSMain() : SV_Target0
+{
+    // Inside func
+    float4 color = float4(0, 0, 0, 0);
+    color.r = DemoSrg::GetRed();
+    color += DemoSrg2::GetColor();
+    return color;
+}

+ 50 - 0
tests/Advanced/struct-pad-to-252.azsl

@@ -0,0 +1,50 @@
+// padding to 252, causes the full test coverage
+// of the three phases of padding as done in StructPadToMutator::InsertPaddingVariables(...)
+// 1- The first variable will be added if @startingOffset is not 16-bytes aligned. It will be a float, float2 or float3.
+// 2- If more bytes are still needed, then We'll add ONE float4[N] array, Until (N * 16) bytes fit within the bytes that are left to add.
+// 3- Finally, if there are more remaining bytes to the be added, a third float, float2 or float3 will be added.
+
+ShaderResourceGroupSemantic slot1
+{
+    FrequencyId = 1;
+};
+
+struct MyStructA
+{
+    float m_data;
+    [[pad_to(16)]]
+};
+
+struct MyStructB
+{
+    int m_data;
+    [[pad_to(16)]]
+    MyStructA m_a;
+    float2 m_b;
+    [[pad_to(64)]]
+};
+
+struct MyStruct
+{
+    MyStructA m_a;
+    MyStructB m_b;
+    float m_color_red;
+    [[pad_to(16)]]
+    uint m_uint;
+    [[pad_to(252)]]
+};
+
+ShaderResourceGroup DemoSrg : slot1
+{
+    MyStruct m_myStruct;
+    StructuredBuffer<MyStruct> m_mySB;
+};
+
+float4 PSMain() : SV_Target0
+{
+    // Inside func
+    float4 color = float4(0, 0, 0, 0);
+    color.r = DemoSrg::m_myStruct.m_color_red;
+    color.r += DemoSrg::m_mySB[0].m_color_red;
+    return color;
+}

+ 44 - 0
tests/Advanced/struct-pad-to-256.azsl

@@ -0,0 +1,44 @@
+ShaderResourceGroupSemantic slot1
+{
+    FrequencyId = 1;
+};
+
+struct MyStructA
+{
+    float m_data;
+    [[pad_to(16)]]
+};
+
+struct MyStructB
+{
+    int m_data;
+    [[pad_to(16)]]
+    MyStructA m_a;
+    float2 m_b;
+    [[pad_to(64)]]
+};
+
+struct MyStruct
+{
+    MyStructA m_a;
+    MyStructB m_b;
+    float m_color_red;
+    [[pad_to(16)]]
+    uint m_uint;
+    [[pad_to(256)]]
+};
+
+ShaderResourceGroup DemoSrg : slot1
+{
+    MyStruct m_myStruct;
+    StructuredBuffer<MyStruct> m_mySB;
+};
+
+float4 PSMain() : SV_Target0
+{
+    // Inside func
+    float4 color = float4(0, 0, 0, 0);
+    color.r = DemoSrg::m_myStruct.m_color_red;
+    color.r += DemoSrg::m_mySB[0].m_color_red;
+    return color;
+}

+ 24 - 0
tests/Semantic/AsError/pad-to-at-struct-begin.azsl

@@ -0,0 +1,24 @@
+// It is an error to start a struct with a [[pad_to(N)]] attribute
+
+ShaderResourceGroupSemantic slot1
+{
+    FrequencyId = 1;
+};
+
+struct MyStruct
+{
+    [[pad_to(16)]] // EC 133
+    float m_red;
+};
+
+ShaderResourceGroup DemoSrg : slot1
+{
+    MyStruct m_data;
+};
+
+float4 PSMain() : SV_Target0
+{
+    float4 color = float4(0, 0, 0, 0);
+    color.r = DemoSrg::m_data.m_red;
+    return color;
+}

+ 25 - 0
tests/Semantic/AsError/pad-to-consecutive.azsl

@@ -0,0 +1,25 @@
+// It is an error to use consecutive [[pad_to(N)]] attributes
+
+ShaderResourceGroupSemantic slot1
+{
+    FrequencyId = 1;
+};
+
+struct MyStruct
+{
+    float m_red;
+    [[pad_to(16)]]
+    [[pad_to(32)]] // EC 133
+};
+
+ShaderResourceGroup DemoSrg : slot1
+{
+    MyStruct m_data;
+};
+
+float4 PSMain() : SV_Target0
+{
+    float4 color = float4(0, 0, 0, 0);
+    color.r = DemoSrg::m_data.m_red;
+    return color;
+}

+ 24 - 0
tests/Semantic/AsError/pad-to-multi-args.azsl

@@ -0,0 +1,24 @@
+// It is an error to provide more than one argument in a [[pad_to(N)]] attribute
+
+ShaderResourceGroupSemantic slot1
+{
+    FrequencyId = 1;
+};
+
+struct MyStruct
+{
+    float m_red;
+    [[pad_to(16,32)]] // EC 132
+};
+
+ShaderResourceGroup DemoSrg : slot1
+{
+    MyStruct m_data;
+};
+
+float4 PSMain() : SV_Target0
+{
+    float4 color = float4(0, 0, 0, 0);
+    color.r = DemoSrg::m_data.m_red;
+    return color;
+}

+ 24 - 0
tests/Semantic/AsError/pad-to-non-multiple-of-4.azsl

@@ -0,0 +1,24 @@
+// It is an error to provide a non-multiple of 4 argument in a [[pad_to(N)]] attribute
+
+ShaderResourceGroupSemantic slot1
+{
+    FrequencyId = 1;
+};
+
+struct MyStruct
+{
+    float m_red;
+    [[pad_to(18)]] // EC 132
+};
+
+ShaderResourceGroup DemoSrg : slot1
+{
+    MyStruct m_data;
+};
+
+float4 PSMain() : SV_Target0
+{
+    float4 color = float4(0, 0, 0, 0);
+    color.r = DemoSrg::m_data.m_red;
+    return color;
+}

+ 24 - 0
tests/Semantic/AsError/pad-to-requires-power-of-two.azsl

@@ -0,0 +1,24 @@
+// It is an error to request a non-power of two [[pad_to(N)]] if the next offset is smaller than
+// N.
+
+ShaderResourceGroupSemantic slot1
+{
+    FrequencyId = 1;
+};
+
+struct MyStruct
+{
+    float4 m_color;
+    [[pad_to(12)]] // #EC 134
+};
+
+ShaderResourceGroup DemoSrg : slot1
+{
+    MyStruct m_data;
+};
+
+float4 PSMain() : SV_Target0
+{
+    float4 color = DemoSrg::m_data.m_color;
+    return color;
+}

+ 24 - 0
tests/Semantic/AsError/pad-to-wrong-args.azsl

@@ -0,0 +1,24 @@
+// It is an error to use [[pad_to(N)]] with a non-integral N Argument.
+
+ShaderResourceGroupSemantic slot1
+{
+    FrequencyId = 1;
+};
+
+struct MyStruct
+{
+    float m_red;
+    [[pad_to("16")]] // #EC 132
+};
+
+ShaderResourceGroup DemoSrg : slot1
+{
+    MyStruct m_data;
+};
+
+float4 PSMain() : SV_Target0
+{
+    float4 color = float4(0, 0, 0, 0);
+    color.r = DemoSrg::m_data.m_red;
+    return color;
+}

+ 25 - 0
tests/Syntax/AsErrors/pad-to-in-function.azsl

@@ -0,0 +1,25 @@
+// It is an error to use [[pad_to(N)]] in a function (outside of a struct, in general).
+
+ShaderResourceGroupSemantic slot1
+{
+    FrequencyId = 1;
+};
+
+struct MyStruct
+{
+    float m_red;
+    [[pad_to(16)]]
+};
+
+ShaderResourceGroup DemoSrg : slot1
+{
+    MyStruct m_data;
+};
+
+float4 PSMain() : SV_Target0
+{
+    float4 color = float4(0, 0, 0, 0);
+    [[pad_to(16)]]
+    color.r = DemoSrg::m_data.m_red;
+    return color;
+}

Some files were not shown because too many files changed in this diff