Vulkan: Add dumping of the command graph to GraphViz.

Dot files are a graph format that can be visualized with GraphViz.
Giving a small visualization output can help diagnose problems with
the graph. For example extra edges or incorrect dependencies.

Bug: angleproject:2379
Change-Id: I544cba11c5e33579b06fef2fb41bad60066a64e4
Reviewed-on: https://chromium-review.googlesource.com/1254383
Commit-Queue: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Tobin Ehlis <tobine@google.com>
diff --git a/src/libANGLE/renderer/vulkan/CommandGraph.cpp b/src/libANGLE/renderer/vulkan/CommandGraph.cpp
index 3dd7467..ca59f87 100644
--- a/src/libANGLE/renderer/vulkan/CommandGraph.cpp
+++ b/src/libANGLE/renderer/vulkan/CommandGraph.cpp
@@ -9,6 +9,8 @@
 
 #include "libANGLE/renderer/vulkan/CommandGraph.h"
 
+#include <iostream>
+
 #include "libANGLE/renderer/vulkan/RenderTargetVk.h"
 #include "libANGLE/renderer/vulkan/RendererVk.h"
 #include "libANGLE/renderer/vulkan/vk_format_utils.h"
@@ -16,13 +18,10 @@
 
 namespace rx
 {
-
 namespace vk
 {
-
 namespace
 {
-
 angle::Result InitAndBeginCommandBuffer(vk::Context *context,
                                         const CommandPool &commandPool,
                                         const VkCommandBufferInheritanceInfo &inheritanceInfo,
@@ -50,10 +49,26 @@
     return angle::Result::Continue();
 }
 
+const char *GetResourceTypeName(CommandGraphResourceType resourceType)
+{
+    switch (resourceType)
+    {
+        case CommandGraphResourceType::Buffer:
+            return "Buffer";
+        case CommandGraphResourceType::Framebuffer:
+            return "Framebuffer";
+        case CommandGraphResourceType::Image:
+            return "Image";
+        default:
+            UNREACHABLE();
+            return "";
+    }
+}
 }  // anonymous namespace
 
 // CommandGraphResource implementation.
-CommandGraphResource::CommandGraphResource() : mCurrentWritingNode(nullptr)
+CommandGraphResource::CommandGraphResource(CommandGraphResourceType resourceType)
+    : mCurrentWritingNode(nullptr), mResourceType(resourceType)
 {
 }
 
@@ -145,6 +160,7 @@
 void CommandGraphResource::finishCurrentCommands(RendererVk *renderer)
 {
     CommandGraphNode *newCommands = renderer->getCommandGraph()->allocateNode();
+    newCommands->setDiagnosticInfo(mResourceType, reinterpret_cast<uintptr_t>(this));
     onWriteImpl(newCommands, renderer->getCurrentQueueSerial());
 }
 
@@ -390,13 +406,28 @@
     return angle::Result::Continue();
 }
 
+const std::vector<CommandGraphNode *> &CommandGraphNode::getParentsForDiagnostics() const
+{
+    return mParents;
+}
+
+void CommandGraphNode::setDiagnosticInfo(CommandGraphResourceType resourceType,
+                                         uintptr_t resourceID)
+{
+    mResourceType = resourceType;
+    mResourceID   = resourceID;
+}
+
 const gl::Rectangle &CommandGraphNode::getRenderPassRenderArea() const
 {
     return mRenderPassRenderArea;
 }
 
 // CommandGraph implementation.
-CommandGraph::CommandGraph() = default;
+CommandGraph::CommandGraph(bool enableGraphDiagnostics)
+    : mEnableGraphDiagnostics(enableGraphDiagnostics)
+{
+}
 
 CommandGraph::~CommandGraph()
 {
@@ -431,6 +462,11 @@
         return angle::Result::Continue();
     }
 
+    if (mEnableGraphDiagnostics)
+    {
+        dumpGraphDotFile(std::cout);
+    }
+
     std::vector<CommandGraphNode *> nodeStack;
 
     VkCommandBufferBeginInfo beginInfo;
@@ -491,5 +527,80 @@
     return mNodes.empty();
 }
 
+// Dumps the command graph into a dot file that works with graphviz.
+void CommandGraph::dumpGraphDotFile(std::ostream &out) const
+{
+    // This ID maps a node pointer to a monatonic ID. It allows us to look up parent node IDs.
+    std::map<const CommandGraphNode *, int> nodeIDMap;
+    std::map<uintptr_t, int> objectIDMap;
+
+    // Map nodes to ids.
+    for (size_t nodeIndex = 0; nodeIndex < mNodes.size(); ++nodeIndex)
+    {
+        const CommandGraphNode *node = mNodes[nodeIndex];
+        nodeIDMap[node]              = static_cast<int>(nodeIndex) + 1;
+    }
+
+    int bufferIDCounter      = 1;
+    int framebufferIDCounter = 1;
+    int imageIDCounter       = 1;
+
+    out << "digraph {" << std::endl;
+
+    for (const CommandGraphNode *node : mNodes)
+    {
+        int nodeID = nodeIDMap[node];
+
+        std::stringstream strstr;
+        strstr << GetResourceTypeName(node->getResourceTypeForDiagnostics());
+        strstr << " ";
+
+        auto it = objectIDMap.find(node->getResourceIDForDiagnostics());
+        if (it != objectIDMap.end())
+        {
+            strstr << it->second;
+        }
+        else
+        {
+            int id = 0;
+
+            switch (node->getResourceTypeForDiagnostics())
+            {
+                case CommandGraphResourceType::Buffer:
+                    id = bufferIDCounter++;
+                    break;
+                case CommandGraphResourceType::Framebuffer:
+                    id = framebufferIDCounter++;
+                    break;
+                case CommandGraphResourceType::Image:
+                    id = imageIDCounter++;
+                    break;
+                default:
+                    UNREACHABLE();
+                    break;
+            }
+
+            objectIDMap[node->getResourceIDForDiagnostics()] = id;
+            strstr << id;
+        }
+
+        const std::string &label = strstr.str();
+        out << "  " << nodeID << "[label =<" << label << "<BR/> <FONT POINT-SIZE=\"10\">Node ID "
+            << nodeID << "</FONT>>];" << std::endl;
+    }
+
+    for (const CommandGraphNode *node : mNodes)
+    {
+        int nodeID = nodeIDMap[node];
+
+        for (const CommandGraphNode *parent : node->getParentsForDiagnostics())
+        {
+            int parentID = nodeIDMap[parent];
+            out << "  " << parentID << " -> " << nodeID << ";" << std::endl;
+        }
+    }
+
+    out << "}" << std::endl;
+}
 }  // namespace vk
 }  // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/CommandGraph.h b/src/libANGLE/renderer/vulkan/CommandGraph.h
index 447ddd1..894f6ae 100644
--- a/src/libANGLE/renderer/vulkan/CommandGraph.h
+++ b/src/libANGLE/renderer/vulkan/CommandGraph.h
@@ -24,6 +24,13 @@
     Visited,
 };
 
+enum class CommandGraphResourceType
+{
+    Buffer,
+    Framebuffer,
+    Image,
+};
+
 // Only used internally in the command graph. Kept in the header for better inlining performance.
 class CommandGraphNode final : angle::NonCopyable
 {
@@ -72,6 +79,13 @@
                                   RenderPassCache *renderPassCache,
                                   CommandBuffer *primaryCommandBuffer);
 
+    // Only used in the command graph diagnostics.
+    const std::vector<CommandGraphNode *> &getParentsForDiagnostics() const;
+    void setDiagnosticInfo(CommandGraphResourceType resourceType, uintptr_t resourceID);
+
+    CommandGraphResourceType getResourceTypeForDiagnostics() const { return mResourceType; }
+    uintptr_t getResourceIDForDiagnostics() const { return mResourceID; }
+
     const gl::Rectangle &getRenderPassRenderArea() const;
 
   private:
@@ -99,6 +113,10 @@
 
     // Used when traversing the dependency graph.
     VisitedState mVisitedState;
+
+    // Additional diagnostic information.
+    CommandGraphResourceType mResourceType;
+    uintptr_t mResourceID;
 };
 
 // This is a helper class for back-end objects used in Vk command buffers. It records a serial
@@ -107,10 +125,9 @@
 // queue serial in a special 'garbage' queue. Resources also track current read and write
 // dependencies. Only one command buffer node can be writing to the Resource at a time, but many
 // can be reading from it. Together the dependencies will form a command graph at submission time.
-class CommandGraphResource
+class CommandGraphResource : angle::NonCopyable
 {
   public:
-    CommandGraphResource();
     virtual ~CommandGraphResource();
 
     // Returns true if the resource is in use by the renderer.
@@ -147,6 +164,8 @@
     void finishCurrentCommands(RendererVk *renderer);
 
   protected:
+    explicit CommandGraphResource(CommandGraphResourceType resourceType);
+
     // Get the current queue serial for this resource. Only used to release resources.
     Serial getStoredQueueSerial() const;
 
@@ -173,6 +192,9 @@
     Serial mStoredQueueSerial;
     std::vector<CommandGraphNode *> mCurrentReadingNodes;
     CommandGraphNode *mCurrentWritingNode;
+
+    // Additional diagnostic information.
+    CommandGraphResourceType mResourceType;
 };
 
 // Translating OpenGL commands into Vulkan and submitting them immediately loses out on some
@@ -199,7 +221,7 @@
 class CommandGraph final : angle::NonCopyable
 {
   public:
-    CommandGraph();
+    explicit CommandGraph(bool enableGraphDiagnostics);
     ~CommandGraph();
 
     // Allocates a new CommandGraphNode and adds it to the list of current open nodes. No ordering
@@ -215,9 +237,11 @@
     bool empty() const;
 
   private:
-    std::vector<CommandGraphNode *> mNodes;
-};
+    void dumpGraphDotFile(std::ostream &out) const;
 
+    std::vector<CommandGraphNode *> mNodes;
+    bool mEnableGraphDiagnostics;
+};
 }  // namespace vk
 }  // namespace rx
 
diff --git a/src/libANGLE/renderer/vulkan/RendererVk.cpp b/src/libANGLE/renderer/vulkan/RendererVk.cpp
index dfb0619..91ecf3f 100644
--- a/src/libANGLE/renderer/vulkan/RendererVk.cpp
+++ b/src/libANGLE/renderer/vulkan/RendererVk.cpp
@@ -261,6 +261,9 @@
     *physicalDeviceOut = physicalDevices[0];
     vkGetPhysicalDeviceProperties(*physicalDeviceOut, physicalDevicePropertiesOut);
 }
+
+// Initially dumping the command graphs is disabled.
+constexpr bool kEnableCommandGraphDiagnostics = false;
 }  // anonymous namespace
 
 // CommandBatch implementation.
@@ -300,7 +303,8 @@
       mLastCompletedQueueSerial(mQueueSerialFactory.generate()),
       mCurrentQueueSerial(mQueueSerialFactory.generate()),
       mDeviceLost(false),
-      mPipelineCacheVkUpdateTimeout(kPipelineCacheVkUpdatePeriod)
+      mPipelineCacheVkUpdateTimeout(kPipelineCacheVkUpdatePeriod),
+      mCommandGraph(kEnableCommandGraphDiagnostics)
 {
 }
 
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.cpp b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
index 254e976..d220825 100644
--- a/src/libANGLE/renderer/vulkan/vk_helpers.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
@@ -504,7 +504,8 @@
 }
 
 // BufferHelper implementation.
-BufferHelper::BufferHelper() : mMemoryPropertyFlags{}
+BufferHelper::BufferHelper()
+    : CommandGraphResource(CommandGraphResourceType::Buffer), mMemoryPropertyFlags{}
 {
 }
 
@@ -527,12 +528,17 @@
 
 // ImageHelper implementation.
 ImageHelper::ImageHelper()
-    : mFormat(nullptr), mSamples(0), mCurrentLayout(VK_IMAGE_LAYOUT_UNDEFINED), mLayerCount(0)
+    : CommandGraphResource(CommandGraphResourceType::Image),
+      mFormat(nullptr),
+      mSamples(0),
+      mCurrentLayout(VK_IMAGE_LAYOUT_UNDEFINED),
+      mLayerCount(0)
 {
 }
 
 ImageHelper::ImageHelper(ImageHelper &&other)
-    : mImage(std::move(other.mImage)),
+    : CommandGraphResource(CommandGraphResourceType::Image),
+      mImage(std::move(other.mImage)),
       mDeviceMemory(std::move(other.mDeviceMemory)),
       mExtents(other.mExtents),
       mFormat(other.mFormat),
@@ -935,7 +941,9 @@
 }
 
 // FramebufferHelper implementation.
-FramebufferHelper::FramebufferHelper() = default;
+FramebufferHelper::FramebufferHelper() : CommandGraphResource(CommandGraphResourceType::Framebuffer)
+{
+}
 
 FramebufferHelper::~FramebufferHelper() = default;
 
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.h b/src/libANGLE/renderer/vulkan/vk_helpers.h
index 83b57e9..3d3c0bf 100644
--- a/src/libANGLE/renderer/vulkan/vk_helpers.h
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.h
@@ -166,7 +166,7 @@
     DynamicBuffer mDynamicIndexBuffer;
 };
 
-class BufferHelper final : public CommandGraphResource, angle::NonCopyable
+class BufferHelper final : public CommandGraphResource
 {
   public:
     BufferHelper();
@@ -190,7 +190,7 @@
     VkMemoryPropertyFlags mMemoryPropertyFlags;
 };
 
-class ImageHelper final : public CommandGraphResource, angle::NonCopyable
+class ImageHelper final : public CommandGraphResource
 {
   public:
     ImageHelper();
@@ -299,7 +299,7 @@
     uint32_t mLayerCount;
 };
 
-class FramebufferHelper : public CommandGraphResource, angle::NonCopyable
+class FramebufferHelper : public CommandGraphResource
 {
   public:
     FramebufferHelper();