Vulkan: add uniform buffer object support

Support for layout qualifiers in interface blocks are added.  All
interface blocks are adjusted to either be in std140 or std430.

In the Vulkan backend, a new descriptor set is added for UBOs.  A dirty
bit is added for UBO updating and pipeline layouts and descriptor
bindings are updated.

Bug: angleproject:3199, angleproject:3220
Change-Id: I271fc34ac2e1e8b76dee75e54a7cff0fe15fe4ee
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1565061
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
diff --git a/src/compiler/translator/Compiler.cpp b/src/compiler/translator/Compiler.cpp
index a31eda7..bdfb7f3 100644
--- a/src/compiler/translator/Compiler.cpp
+++ b/src/compiler/translator/Compiler.cpp
@@ -33,6 +33,7 @@
 #include "compiler/translator/tree_ops/InitializeVariables.h"
 #include "compiler/translator/tree_ops/PruneEmptyCases.h"
 #include "compiler/translator/tree_ops/PruneNoOps.h"
+#include "compiler/translator/tree_ops/RedefineInterfaceBlockLayoutQualifiersWithStd.h"
 #include "compiler/translator/tree_ops/RegenerateStructNames.h"
 #include "compiler/translator/tree_ops/RemoveArrayLengthMethod.h"
 #include "compiler/translator/tree_ops/RemoveInvariantDeclaration.h"
@@ -723,6 +724,13 @@
         }
     }
 
+    if (compileOptions & SH_REDEFINE_INTERFACE_LAYOUT_QUALIFIERS_WITH_STD)
+    {
+        // Change the interface block layouts based on GL_KHR_vulkan_glsl.  Only std140 and std430
+        // are allowed in Vulkan GLSL.
+        RedefineInterfaceBlockLayoutQualifiersWithStd(root, &mSymbolTable);
+    }
+
     if (shouldCollectVariables(compileOptions))
     {
         ASSERT(!mVariablesCollected);
diff --git a/src/compiler/translator/OutputGLSLBase.cpp b/src/compiler/translator/OutputGLSLBase.cpp
index 21396ea..254177c 100644
--- a/src/compiler/translator/OutputGLSLBase.cpp
+++ b/src/compiler/translator/OutputGLSLBase.cpp
@@ -61,11 +61,12 @@
   private:
     bool mFirst;
 
-    friend TInfoSinkBase &operator<<(TInfoSinkBase &out,
-                                     CommaSeparatedListItemPrefixGenerator &gen);
+    template <typename Stream>
+    friend Stream &operator<<(Stream &out, CommaSeparatedListItemPrefixGenerator &gen);
 };
 
-TInfoSinkBase &operator<<(TInfoSinkBase &out, CommaSeparatedListItemPrefixGenerator &gen)
+template <typename Stream>
+Stream &operator<<(Stream &out, CommaSeparatedListItemPrefixGenerator &gen)
 {
     if (gen.mFirst)
     {
@@ -160,6 +161,51 @@
     }
 }
 
+// Outputs what goes inside layout(), except for location and binding qualifiers, as they are
+// handled differently between GL GLSL and Vulkan GLSL.
+std::string TOutputGLSLBase::getCommonLayoutQualifiers(TIntermTyped *variable)
+{
+    std::ostringstream out;
+    CommaSeparatedListItemPrefixGenerator listItemPrefix;
+
+    const TType &type                       = variable->getType();
+    const TLayoutQualifier &layoutQualifier = type.getLayoutQualifier();
+
+    if (type.getQualifier() == EvqFragmentOut || type.getQualifier() == EvqVertexIn ||
+        IsVarying(type.getQualifier()))
+    {
+        if (type.getQualifier() == EvqFragmentOut && layoutQualifier.index >= 0)
+        {
+            out << listItemPrefix << "index = " << layoutQualifier.index;
+        }
+    }
+
+    if (type.getQualifier() == EvqFragmentOut)
+    {
+        if (layoutQualifier.yuv == true)
+        {
+            out << listItemPrefix << "yuv";
+        }
+    }
+
+    if (IsImage(type.getBasicType()))
+    {
+        if (layoutQualifier.imageInternalFormat != EiifUnspecified)
+        {
+            ASSERT(type.getQualifier() == EvqTemporary || type.getQualifier() == EvqUniform);
+            out << listItemPrefix
+                << getImageInternalFormatString(layoutQualifier.imageInternalFormat);
+        }
+    }
+
+    if (IsAtomicCounter(type.getBasicType()))
+    {
+        out << listItemPrefix << "offset = " << layoutQualifier.offset;
+    }
+
+    return out.str();
+}
+
 void TOutputGLSLBase::writeLayoutQualifier(TIntermTyped *variable)
 {
     const TType &type = variable->getType();
@@ -189,18 +235,6 @@
         {
             out << listItemPrefix << "location = " << layoutQualifier.location;
         }
-        if (type.getQualifier() == EvqFragmentOut && layoutQualifier.index >= 0)
-        {
-            out << listItemPrefix << "index = " << layoutQualifier.index;
-        }
-    }
-
-    if (type.getQualifier() == EvqFragmentOut)
-    {
-        if (layoutQualifier.yuv == true)
-        {
-            out << listItemPrefix << "yuv";
-        }
     }
 
     if (IsOpaqueType(type.getBasicType()))
@@ -211,25 +245,16 @@
         }
     }
 
-    if (IsImage(type.getBasicType()))
+    std::string otherQualifiers = getCommonLayoutQualifiers(variable);
+    if (!otherQualifiers.empty())
     {
-        if (layoutQualifier.imageInternalFormat != EiifUnspecified)
-        {
-            ASSERT(type.getQualifier() == EvqTemporary || type.getQualifier() == EvqUniform);
-            out << listItemPrefix
-                << getImageInternalFormatString(layoutQualifier.imageInternalFormat);
-        }
-    }
-
-    if (IsAtomicCounter(type.getBasicType()))
-    {
-        out << listItemPrefix << "offset = " << layoutQualifier.offset;
+        out << listItemPrefix << otherQualifiers;
     }
 
     out << ") ";
 }
 
-void TOutputGLSLBase::writeQualifier(TQualifier qualifier, const TSymbol *symbol)
+void TOutputGLSLBase::writeQualifier(TQualifier qualifier, const TType &type, const TSymbol *symbol)
 {
     const char *result = mapQualifierToString(qualifier);
     if (result && result[0] != '\0')
@@ -284,7 +309,7 @@
     }
     if (qualifier != EvqTemporary && qualifier != EvqGlobal)
     {
-        writeQualifier(qualifier, symbol);
+        writeQualifier(qualifier, type, symbol);
     }
 
     const TMemoryQualifier &memoryQualifier = type.getMemoryQualifier();
diff --git a/src/compiler/translator/OutputGLSLBase.h b/src/compiler/translator/OutputGLSLBase.h
index 96f767e..feeffee 100644
--- a/src/compiler/translator/OutputGLSLBase.h
+++ b/src/compiler/translator/OutputGLSLBase.h
@@ -40,6 +40,7 @@
     TInfoSinkBase &objSink() { return mObjSink; }
     void writeFloat(TInfoSinkBase &out, float f);
     void writeTriplet(Visit visit, const char *preStr, const char *inStr, const char *postStr);
+    std::string getCommonLayoutQualifiers(TIntermTyped *variable);
     virtual void writeLayoutQualifier(TIntermTyped *variable);
     void writeInvariantQualifier(const TType &type);
     virtual void writeVariableType(const TType &type, const TSymbol *symbol);
@@ -77,7 +78,7 @@
     virtual ImmutableString translateTextureFunction(const ImmutableString &name) { return name; }
 
     void declareStruct(const TStructure *structure);
-    virtual void writeQualifier(TQualifier qualifier, const TSymbol *symbol);
+    virtual void writeQualifier(TQualifier qualifier, const TType &type, const TSymbol *symbol);
     bool structDeclared(const TStructure *structure) const;
 
   private:
diff --git a/src/compiler/translator/OutputVulkanGLSL.cpp b/src/compiler/translator/OutputVulkanGLSL.cpp
index 2075cfe..73b76de 100644
--- a/src/compiler/translator/OutputVulkanGLSL.cpp
+++ b/src/compiler/translator/OutputVulkanGLSL.cpp
@@ -61,32 +61,73 @@
     TIntermSymbol *symbol = variable->getAsSymbolNode();
     ASSERT(symbol);
 
+    ImmutableString name      = symbol->getName();
+    const char *blockStorage  = nullptr;
+    const char *matrixPacking = nullptr;
+
+    // For interface blocks, use the block name instead.  When the layout qualifier is being
+    // replaced in the backend, that would be the name that's available.
+    if (type.isInterfaceBlock())
+    {
+        const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock();
+        name                                  = interfaceBlock->name();
+
+        // Make sure block storage format is specified.
+        if (interfaceBlock->blockStorage() != EbsUnspecified)
+        {
+            blockStorage = getBlockStorageString(interfaceBlock->blockStorage());
+        }
+    }
+
+    // Specify matrix packing if necessary.
+    if (layoutQualifier.matrixPacking != EmpUnspecified)
+    {
+        matrixPacking = getMatrixPackingString(layoutQualifier.matrixPacking);
+    }
+
     if (needsCustomLayout)
     {
-        out << "@@ LAYOUT-" << symbol->getName() << "() @@";
+        out << "@@ LAYOUT-" << name << "(";
     }
     else
     {
         out << "layout(";
     }
 
-    if (IsImage(type.getBasicType()) && layoutQualifier.imageInternalFormat != EiifUnspecified)
+    // Output the list of qualifiers already known at this stage, i.e. everything other than
+    // `location` and `set`/`binding`.
+    std::string otherQualifiers = getCommonLayoutQualifiers(variable);
+
+    const char *separator = "";
+    if (blockStorage)
     {
-        ASSERT(type.getQualifier() == EvqTemporary || type.getQualifier() == EvqUniform);
-        out << getImageInternalFormatString(layoutQualifier.imageInternalFormat);
+        out << separator << blockStorage;
+        separator = ", ";
+    }
+    if (matrixPacking)
+    {
+        out << separator << matrixPacking;
+        separator = ", ";
+    }
+    if (!otherQualifiers.empty())
+    {
+        out << separator << otherQualifiers;
     }
 
-    if (!needsCustomLayout)
+    out << ") ";
+    if (needsCustomLayout)
     {
-        out << ") ";
+        out << "@@";
     }
 }
 
-void TOutputVulkanGLSL::writeQualifier(TQualifier qualifier, const TSymbol *symbol)
+void TOutputVulkanGLSL::writeQualifier(TQualifier qualifier,
+                                       const TType &type,
+                                       const TSymbol *symbol)
 {
     if (qualifier != EvqUniform && qualifier != EvqAttribute && !sh::IsVarying(qualifier))
     {
-        TOutputGLSLBase::writeQualifier(qualifier, symbol);
+        TOutputGLSLBase::writeQualifier(qualifier, type, symbol);
         return;
     }
 
@@ -95,8 +136,17 @@
         return;
     }
 
+    ImmutableString name = symbol->name();
+
+    // For interface blocks, use the block name instead.  When the qualifier is being replaced in
+    // the backend, that would be the name that's available.
+    if (type.isInterfaceBlock())
+    {
+        name = type.getInterfaceBlock()->name();
+    }
+
     TInfoSinkBase &out = objSink();
-    out << "@@ QUALIFIER-" << symbol->name().data() << " @@ ";
+    out << "@@ QUALIFIER-" << name.data() << " @@ ";
 }
 
 void TOutputVulkanGLSL::writeVariableType(const TType &type, const TSymbol *symbol)
diff --git a/src/compiler/translator/OutputVulkanGLSL.h b/src/compiler/translator/OutputVulkanGLSL.h
index ef49c97..717801d 100644
--- a/src/compiler/translator/OutputVulkanGLSL.h
+++ b/src/compiler/translator/OutputVulkanGLSL.h
@@ -31,7 +31,7 @@
 
   protected:
     void writeLayoutQualifier(TIntermTyped *variable) override;
-    void writeQualifier(TQualifier qualifier, const TSymbol *symbol) override;
+    void writeQualifier(TQualifier qualifier, const TType &type, const TSymbol *symbol) override;
     void writeVariableType(const TType &type, const TSymbol *symbol) override;
 };
 
diff --git a/src/compiler/translator/tree_ops/RedefineInterfaceBlockLayoutQualifiersWithStd.cpp b/src/compiler/translator/tree_ops/RedefineInterfaceBlockLayoutQualifiersWithStd.cpp
new file mode 100644
index 0000000..a32281f
--- /dev/null
+++ b/src/compiler/translator/tree_ops/RedefineInterfaceBlockLayoutQualifiersWithStd.cpp
@@ -0,0 +1,107 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RedefineInterfaceBlockLayoutQualifiersWithStd: Ensure layout qualifiers are either std140 or
+// std430 to comply with Vulkan GLSL.
+//
+
+#include "compiler/translator/tree_ops/RedefineInterfaceBlockLayoutQualifiersWithStd.h"
+
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+namespace
+{
+// Helper to replace the type of a symbol
+TIntermSymbol *RedefineLayoutQualifierOfSymbolNode(TIntermSymbol *symbolNode,
+                                                   const TLayoutQualifier &newLayoutQualifier,
+                                                   TSymbolTable *symbolTable)
+{
+    const TVariable &oldVariable = symbolNode->variable();
+
+    ASSERT(symbolNode->getType().isInterfaceBlock());
+
+    const TType &oldType                     = symbolNode->getType();
+    const TInterfaceBlock *oldInterfaceBlock = oldType.getInterfaceBlock();
+
+    // Create a new type based on the old type, but the memory layout qualifier changed.
+    TType *newType = new TType(oldType);
+    newType->setLayoutQualifier(newLayoutQualifier);
+
+    // Create a new interface block based on the old one, with the new memory layout qualifier as
+    // well.
+    TInterfaceBlock *newInterfaceBlock =
+        new TInterfaceBlock(symbolTable, oldInterfaceBlock->name(), &oldInterfaceBlock->fields(),
+                            newLayoutQualifier, oldInterfaceBlock->symbolType());
+
+    newType->setInterfaceBlock(newInterfaceBlock);
+
+    // Create a new variable with the modified type, to substitute the old variable.
+    TVariable *newVariable =
+        new TVariable(oldVariable.uniqueId(), oldVariable.name(), oldVariable.symbolType(),
+                      oldVariable.extension(), newType);
+
+    return new TIntermSymbol(newVariable);
+}
+
+class Traverser : public TIntermTraverser
+{
+  public:
+    explicit Traverser(TSymbolTable *symbolTable)
+        : TIntermTraverser(true, false, false, symbolTable)
+    {
+        symbolTable->push();
+    }
+
+    ~Traverser() override { mSymbolTable->pop(); }
+
+    bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
+    {
+        ASSERT(visit == PreVisit);
+
+        if (!mInGlobalScope)
+        {
+            return false;
+        }
+
+        const TIntermSequence &sequence = *(node->getSequence());
+        TIntermTyped *declarator        = sequence.front()->getAsTyped();
+        const TType &type               = declarator->getType();
+
+        if (type.isInterfaceBlock())
+        {
+            ASSERT(declarator->getAsSymbolNode());
+
+            TLayoutQualifier layoutQualifier = type.getLayoutQualifier();
+
+            // If the layout qualifier is not explicitly std140 or std430, change it to std140 for
+            // uniforms and std430 otherwise.  See the comment in the header for more information.
+            if (layoutQualifier.blockStorage != EbsStd140 &&
+                layoutQualifier.blockStorage != EbsStd430)
+            {
+                layoutQualifier.blockStorage =
+                    type.getQualifier() == EvqUniform ? EbsStd140 : EbsStd430;
+
+                TIntermSymbol *replacement = RedefineLayoutQualifierOfSymbolNode(
+                    declarator->getAsSymbolNode(), layoutQualifier, mSymbolTable);
+
+                queueReplacementWithParent(node, declarator, replacement, OriginalNode::IS_DROPPED);
+            }
+        }
+
+        return false;
+    }
+};
+}  // anonymous namespace
+
+void RedefineInterfaceBlockLayoutQualifiersWithStd(TIntermBlock *root, TSymbolTable *symbolTable)
+{
+    Traverser redefineInterfaceBlockLayoutQualifiersWithStd(symbolTable);
+    root->traverse(&redefineInterfaceBlockLayoutQualifiersWithStd);
+    redefineInterfaceBlockLayoutQualifiersWithStd.updateTree();
+}
+}  // namespace sh
diff --git a/src/compiler/translator/tree_ops/RedefineInterfaceBlockLayoutQualifiersWithStd.h b/src/compiler/translator/tree_ops/RedefineInterfaceBlockLayoutQualifiersWithStd.h
new file mode 100644
index 0000000..87b0c39
--- /dev/null
+++ b/src/compiler/translator/tree_ops/RedefineInterfaceBlockLayoutQualifiersWithStd.h
@@ -0,0 +1,25 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RedefineInterfaceBlockLayoutQualifiersWithStd: Change the memory layout qualifier of interface
+// blocks if not specifically requested to be std140 or std430, i.e. the memory layout qualifier is
+// changed if it's unspecified, shared or packed.  This makes the layout qualifiers conformant with
+// Vulkan GLSL (GL_KHR_vulkan_glsl).
+//
+// - For uniform buffers, std140 is used.  It would have been more efficient to default to std430,
+//   but that would require GL_EXT_scalar_block_layout.
+// - For storage buffers, std430 is used.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_REDEFINEINTERFACEBLOCKLAYOUTQUALIFIERSWITHSTD_H_
+#define COMPILER_TRANSLATOR_TREEOPS_REDEFINEINTERFACEBLOCKLAYOUTQUALIFIERSWITHSTD_H_
+
+namespace sh
+{
+class TIntermBlock;
+class TSymbolTable;
+void RedefineInterfaceBlockLayoutQualifiersWithStd(TIntermBlock *root, TSymbolTable *symbolTable);
+}  // namespace sh
+
+#endif  // COMPILER_TRANSLATOR_TREEOPS_REDEFINEINTERFACEBLOCKLAYOUTQUALIFIERSWITHSTD_H_