Edit

kc3-lang/angle/src/libANGLE/renderer/vulkan/CommandGraph.cpp

Branch :

  • Show log

    Commit

  • Author : Jamie Madill
    Date : 2019-09-26 10:27:18
    Hash : f10bf6bf
    Message : Vulkan: Implement multi-threaded GL. The main component of this change is to make vk::BufferHelper, vk::ImageHelper and vk::SyncHelper use a common path. We introduce a new "vk::SharedGarbage" helper class that stores small lists of garbage from individual objects like an ImageHelper or BufferHelper. The SharedGarbage is stored in the RendererVk with the ResourceUse of the helper object. The ResourceUse tells RendererVk when it is safe to destroy the GarbageObjects. New "onGraphAccess" commands are added in a few places to enable the common garbage collection path. A couple Context-only resources like default attributes now are referenced where they were not before. Also reorganizes some functions so we can add a few helpful ASSERTs to our graph dependencies. Added "updateCurrentAccessNodes" for this. Also adds a "RendererScoped" helper to replace many uses of "ContextScoped". The multithreading EGL tests mostly pass but have some remaining flakiness so cannot yet be enabled. Bug: angleproject:2464 Change-Id: Ia3e3ae8848d731abf3f21ebe04c33e381e130be0 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1808444 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: Geoff Lang <geofflang@chromium.org>

  • src/libANGLE/renderer/vulkan/CommandGraph.cpp
  • //
    // Copyright 2017 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.
    //
    // CommandGraph:
    //    Deferred work constructed by GL calls, that will later be flushed to Vulkan.
    //
    
    #include "libANGLE/renderer/vulkan/CommandGraph.h"
    
    #include <iostream>
    
    #include "libANGLE/Overlay.h"
    #include "libANGLE/renderer/vulkan/ContextVk.h"
    #include "libANGLE/renderer/vulkan/RenderTargetVk.h"
    #include "libANGLE/renderer/vulkan/RendererVk.h"
    #include "libANGLE/renderer/vulkan/vk_format_utils.h"
    #include "libANGLE/renderer/vulkan/vk_helpers.h"
    
    #include "libANGLE/trace.h"
    
    namespace rx
    {
    namespace vk
    {
    namespace
    {
    ANGLE_MAYBE_UNUSED
    angle::Result InitAndBeginCommandBuffer(ContextVk *context,
                                            const CommandPool &commandPool,
                                            const VkCommandBufferInheritanceInfo &inheritanceInfo,
                                            VkCommandBufferUsageFlags flags,
                                            angle::PoolAllocator *poolAllocator,
                                            priv::SecondaryCommandBuffer *commandBuffer)
    {
        ASSERT(!commandBuffer->valid());
        commandBuffer->initialize(poolAllocator);
        return angle::Result::Continue;
    }
    
    ANGLE_MAYBE_UNUSED
    angle::Result InitAndBeginCommandBuffer(vk::Context *context,
                                            const CommandPool &commandPool,
                                            const VkCommandBufferInheritanceInfo &inheritanceInfo,
                                            VkCommandBufferUsageFlags flags,
                                            angle::PoolAllocator *poolAllocator,
                                            priv::CommandBuffer *commandBuffer)
    {
        ASSERT(!commandBuffer->valid());
        ASSERT(commandPool.valid());
        VkCommandBufferAllocateInfo createInfo = {};
        createInfo.sType                       = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
        createInfo.commandPool                 = commandPool.getHandle();
        createInfo.level                       = VK_COMMAND_BUFFER_LEVEL_SECONDARY;
        createInfo.commandBufferCount          = 1;
    
        ANGLE_VK_TRY(context, commandBuffer->init(context->getDevice(), createInfo));
    
        VkCommandBufferBeginInfo beginInfo = {};
        beginInfo.sType                    = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
        beginInfo.flags                    = flags | VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
        beginInfo.pInheritanceInfo         = &inheritanceInfo;
    
        ANGLE_VK_TRY(context, commandBuffer->begin(beginInfo));
        return angle::Result::Continue;
    }
    
    const char *GetResourceTypeName(CommandGraphResourceType resourceType,
                                    CommandGraphNodeFunction function)
    {
        switch (resourceType)
        {
            case CommandGraphResourceType::Buffer:
                return "Buffer";
            case CommandGraphResourceType::Framebuffer:
                return "Framebuffer";
            case CommandGraphResourceType::Image:
                return "Image";
            case CommandGraphResourceType::Query:
                switch (function)
                {
                    case CommandGraphNodeFunction::BeginQuery:
                        return "BeginQuery";
                    case CommandGraphNodeFunction::EndQuery:
                        return "EndQuery";
                    case CommandGraphNodeFunction::WriteTimestamp:
                        return "WriteTimestamp";
                    default:
                        UNREACHABLE();
                        return "Query";
                }
            case CommandGraphResourceType::Dispatcher:
                return "Dispatcher";
            case CommandGraphResourceType::EmulatedQuery:
                switch (function)
                {
                    case CommandGraphNodeFunction::BeginTransformFeedbackQuery:
                        return "BeginTransformFeedbackQuery";
                    case CommandGraphNodeFunction::EndTransformFeedbackQuery:
                        return "EndTransformFeedbackQuery";
                    default:
                        UNREACHABLE();
                        return "EmulatedQuery";
                }
            case CommandGraphResourceType::FenceSync:
                switch (function)
                {
                    case CommandGraphNodeFunction::SetFenceSync:
                        return "SetFenceSync";
                    case CommandGraphNodeFunction::WaitFenceSync:
                        return "WaitFenceSync";
                    default:
                        UNREACHABLE();
                        return "FenceSync";
                }
            case CommandGraphResourceType::GraphBarrier:
                return "GraphBarrier";
            case CommandGraphResourceType::DebugMarker:
                switch (function)
                {
                    case CommandGraphNodeFunction::InsertDebugMarker:
                        return "InsertDebugMarker";
                    case CommandGraphNodeFunction::PushDebugMarker:
                        return "PushDebugMarker";
                    case CommandGraphNodeFunction::PopDebugMarker:
                        return "PopDebugMarker";
                    default:
                        UNREACHABLE();
                        return "DebugMarker";
                }
            case CommandGraphResourceType::HostAvailabilityOperation:
                switch (function)
                {
                    case CommandGraphNodeFunction::HostAvailabilityOperation:
                        return "HostAvailabilityOperation";
                    default:
                        UNREACHABLE();
                        return "HostAvailabilityOperation";
                }
            default:
                UNREACHABLE();
                return "";
        }
    }
    
    const char *GetLoadOpShorthand(uint32_t loadOp)
    {
        switch (loadOp)
        {
            case VK_ATTACHMENT_LOAD_OP_CLEAR:
                return "C";
            case VK_ATTACHMENT_LOAD_OP_LOAD:
                return "L";
            default:
                return "D";
        }
    }
    
    const char *GetStoreOpShorthand(uint32_t storeOp)
    {
        switch (storeOp)
        {
            case VK_ATTACHMENT_STORE_OP_STORE:
                return "S";
            default:
                return "D";
        }
    }
    
    void MakeDebugUtilsLabel(GLenum source, const char *marker, VkDebugUtilsLabelEXT *label)
    {
        static constexpr angle::ColorF kLabelColors[6] = {
            angle::ColorF(1.0f, 0.5f, 0.5f, 1.0f),  // DEBUG_SOURCE_API
            angle::ColorF(0.5f, 1.0f, 0.5f, 1.0f),  // DEBUG_SOURCE_WINDOW_SYSTEM
            angle::ColorF(0.5f, 0.5f, 1.0f, 1.0f),  // DEBUG_SOURCE_SHADER_COMPILER
            angle::ColorF(0.7f, 0.7f, 0.7f, 1.0f),  // DEBUG_SOURCE_THIRD_PARTY
            angle::ColorF(0.5f, 0.8f, 0.9f, 1.0f),  // DEBUG_SOURCE_APPLICATION
            angle::ColorF(0.9f, 0.8f, 0.5f, 1.0f),  // DEBUG_SOURCE_OTHER
        };
    
        int colorIndex = source - GL_DEBUG_SOURCE_API;
        ASSERT(colorIndex >= 0 && static_cast<size_t>(colorIndex) < ArraySize(kLabelColors));
    
        label->sType      = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT;
        label->pNext      = nullptr;
        label->pLabelName = marker;
        kLabelColors[colorIndex].writeData(label->color);
    }
    
    constexpr VkSubpassContents kRenderPassContents =
        CommandBuffer::ExecutesInline() ? VK_SUBPASS_CONTENTS_INLINE
                                        : VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS;
    
    // Helpers to unify executeCommands call based on underlying cmd buffer type
    ANGLE_MAYBE_UNUSED
    void ExecuteCommands(PrimaryCommandBuffer *primCmdBuffer,
                         priv::SecondaryCommandBuffer *secCmdBuffer)
    {
        secCmdBuffer->executeCommands(primCmdBuffer->getHandle());
    }
    
    ANGLE_MAYBE_UNUSED
    void ExecuteCommands(PrimaryCommandBuffer *primCmdBuffer, priv::CommandBuffer *secCmdBuffer)
    {
        primCmdBuffer->executeCommands(1, secCmdBuffer);
    }
    
    ANGLE_MAYBE_UNUSED
    std::string DumpCommands(const priv::SecondaryCommandBuffer &commandBuffer, const char *separator)
    {
        return commandBuffer.dumpCommands(separator);
    }
    
    ANGLE_MAYBE_UNUSED
    std::string DumpCommands(const priv::CommandBuffer &commandBuffer, const char *separator)
    {
        return "--blob--";
    }
    
    float CalculateSecondaryCommandBufferPoolWaste(const std::vector<CommandGraphNode *> nodes)
    {
        size_t used      = 0;
        size_t allocated = 0;
    
        for (const CommandGraphNode *node : nodes)
        {
            size_t nodeUsed;
            size_t nodeAllocated;
            node->getMemoryUsageStatsForDiagnostics(&nodeUsed, &nodeAllocated);
            used += nodeUsed;
            allocated += nodeAllocated;
        }
    
        allocated = std::max<size_t>(allocated, 1);
        return static_cast<float>(used) / static_cast<float>(allocated);
    }
    
    }  // anonymous namespace
    
    // CommandGraphResource implementation.
    CommandGraphResource::CommandGraphResource(CommandGraphResourceType resourceType)
        : mCurrentWritingNode(nullptr), mResourceType(resourceType)
    {
        mUse.init();
    }
    
    CommandGraphResource::~CommandGraphResource()
    {
        mUse.release();
    }
    
    bool CommandGraphResource::isResourceInUse(ContextVk *contextVk) const
    {
        return mUse.isCurrentlyInGraph() || contextVk->isSerialInUse(mUse.getSerial());
    }
    
    angle::Result CommandGraphResource::recordCommands(ContextVk *contextVk,
                                                       CommandBuffer **commandBufferOut)
    {
        updateCurrentAccessNodes();
    
        if (!hasChildlessWritingNode() || hasStartedRenderPass())
        {
            startNewCommands(contextVk);
            return mCurrentWritingNode->beginOutsideRenderPassRecording(
                contextVk, contextVk->getCommandPool(), commandBufferOut);
        }
    
        CommandBuffer *outsideRenderPassCommands = mCurrentWritingNode->getOutsideRenderPassCommands();
        if (!outsideRenderPassCommands->valid())
        {
            ANGLE_TRY(mCurrentWritingNode->beginOutsideRenderPassRecording(
                contextVk, contextVk->getCommandPool(), commandBufferOut));
        }
        else
        {
            *commandBufferOut = outsideRenderPassCommands;
        }
    
        // Store reference to usage in graph.
        contextVk->getCommandGraph()->onResourceUse(mUse);
    
        return angle::Result::Continue;
    }
    
    angle::Result CommandGraphResource::beginRenderPass(
        ContextVk *contextVk,
        const Framebuffer &framebuffer,
        const gl::Rectangle &renderArea,
        const RenderPassDesc &renderPassDesc,
        const AttachmentOpsArray &renderPassAttachmentOps,
        const std::vector<VkClearValue> &clearValues,
        CommandBuffer **commandBufferOut)
    {
        // If a barrier has been inserted in the meantime, stop the command buffer.
        if (!hasChildlessWritingNode())
        {
            startNewCommands(contextVk);
        }
    
        mCurrentWritingNode->storeRenderPassInfo(framebuffer, renderArea, renderPassDesc,
                                                 renderPassAttachmentOps, clearValues);
    
        mCurrentWritingNode->setRenderPassOwner(contextVk);
    
        return mCurrentWritingNode->beginInsideRenderPassRecording(contextVk, commandBufferOut);
    }
    
    void CommandGraphResource::addWriteDependency(ContextVk *contextVk,
                                                  CommandGraphResource *writingResource)
    {
        CommandGraphNode *writingNode = writingResource->mCurrentWritingNode;
        ASSERT(writingNode);
    
        onWriteImpl(contextVk, writingNode);
    }
    
    void CommandGraphResource::addReadDependency(ContextVk *contextVk,
                                                 CommandGraphResource *readingResource)
    {
        onGraphAccess(contextVk->getCommandGraph());
    
        CommandGraphNode *readingNode = readingResource->mCurrentWritingNode;
        ASSERT(readingNode);
    
        if (mCurrentWritingNode)
        {
            // Ensure 'readingNode' happens after the current writing node.
            CommandGraphNode::SetHappensBeforeDependency(mCurrentWritingNode, readingNode);
        }
    
        // Add the read node to the list of nodes currently reading this resource.
        mCurrentReadingNodes.push_back(readingNode);
    }
    
    void CommandGraphResource::finishCurrentCommands(ContextVk *contextVk)
    {
        startNewCommands(contextVk);
    }
    
    void CommandGraphResource::startNewCommands(ContextVk *contextVk)
    {
        CommandGraphNode *newCommands =
            contextVk->getCommandGraph()->allocateNode(CommandGraphNodeFunction::Generic);
        newCommands->setDiagnosticInfo(mResourceType, reinterpret_cast<uintptr_t>(this));
        onWriteImpl(contextVk, newCommands);
    }
    
    void CommandGraphResource::onWriteImpl(ContextVk *contextVk, CommandGraphNode *writingNode)
    {
        onGraphAccess(contextVk->getCommandGraph());
    
        // Make sure any open reads and writes finish before we execute 'writingNode'.
        if (!mCurrentReadingNodes.empty())
        {
            CommandGraphNode::SetHappensBeforeDependencies(mCurrentReadingNodes.data(),
                                                           mCurrentReadingNodes.size(), writingNode);
            mCurrentReadingNodes.clear();
        }
    
        if (mCurrentWritingNode && mCurrentWritingNode != writingNode)
        {
            CommandGraphNode::SetHappensBeforeDependency(mCurrentWritingNode, writingNode);
        }
    
        mCurrentWritingNode = writingNode;
    }
    
    // CommandGraphNode implementation.
    CommandGraphNode::CommandGraphNode(CommandGraphNodeFunction function,
                                       angle::PoolAllocator *poolAllocator)
        : mRenderPassClearValues{},
          mFunction(function),
          mPoolAllocator(poolAllocator),
          mQueryPool(VK_NULL_HANDLE),
          mQueryIndex(0),
          mFenceSyncEvent(VK_NULL_HANDLE),
          mHasChildren(false),
          mVisitedState(VisitedState::Unvisited),
          mGlobalMemoryBarrierSrcAccess(0),
          mGlobalMemoryBarrierDstAccess(0),
          mGlobalMemoryBarrierStages(0),
          mRenderPassOwner(nullptr)
    {}
    
    CommandGraphNode::~CommandGraphNode()
    {
        mRenderPassFramebuffer.setHandle(VK_NULL_HANDLE);
        // Command buffers are managed by the command pool, so don't need to be freed.
        mOutsideRenderPassCommands.releaseHandle();
        mInsideRenderPassCommands.releaseHandle();
    }
    
    angle::Result CommandGraphNode::beginOutsideRenderPassRecording(ContextVk *context,
                                                                    const CommandPool &commandPool,
                                                                    CommandBuffer **commandsOut)
    {
        ASSERT(!mHasChildren);
    
        VkCommandBufferInheritanceInfo inheritanceInfo = {};
        inheritanceInfo.sType       = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
        inheritanceInfo.renderPass  = VK_NULL_HANDLE;
        inheritanceInfo.subpass     = 0;
        inheritanceInfo.framebuffer = VK_NULL_HANDLE;
        inheritanceInfo.occlusionQueryEnable =
            CommandBuffer::SupportsQueries(context->getRenderer()->getPhysicalDeviceFeatures());
        inheritanceInfo.queryFlags         = 0;
        inheritanceInfo.pipelineStatistics = 0;
    
        ANGLE_TRY(InitAndBeginCommandBuffer(context, commandPool, inheritanceInfo, 0, mPoolAllocator,
                                            &mOutsideRenderPassCommands));
    
        *commandsOut = &mOutsideRenderPassCommands;
        return angle::Result::Continue;
    }
    
    angle::Result CommandGraphNode::beginInsideRenderPassRecording(ContextVk *context,
                                                                   CommandBuffer **commandsOut)
    {
        ASSERT(!mHasChildren);
    
        // Get a compatible RenderPass from the cache so we can initialize the inheritance info.
        // TODO(jmadill): Support query for compatible/conformant render pass. http://anglebug.com/2361
        RenderPass *compatibleRenderPass;
        ANGLE_TRY(context->getCompatibleRenderPass(mRenderPassDesc, &compatibleRenderPass));
    
        VkCommandBufferInheritanceInfo inheritanceInfo = {};
        inheritanceInfo.sType       = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
        inheritanceInfo.renderPass  = compatibleRenderPass->getHandle();
        inheritanceInfo.subpass     = 0;
        inheritanceInfo.framebuffer = mRenderPassFramebuffer.getHandle();
        inheritanceInfo.occlusionQueryEnable =
            CommandBuffer::SupportsQueries(context->getRenderer()->getPhysicalDeviceFeatures());
        inheritanceInfo.queryFlags         = 0;
        inheritanceInfo.pipelineStatistics = 0;
    
        ANGLE_TRY(InitAndBeginCommandBuffer(context, context->getCommandPool(), inheritanceInfo,
                                            VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT,
                                            mPoolAllocator, &mInsideRenderPassCommands));
    
        *commandsOut = &mInsideRenderPassCommands;
        return angle::Result::Continue;
    }
    
    void CommandGraphNode::storeRenderPassInfo(const Framebuffer &framebuffer,
                                               const gl::Rectangle renderArea,
                                               const vk::RenderPassDesc &renderPassDesc,
                                               const AttachmentOpsArray &renderPassAttachmentOps,
                                               const std::vector<VkClearValue> &clearValues)
    {
        mRenderPassDesc          = renderPassDesc;
        mRenderPassAttachmentOps = renderPassAttachmentOps;
        mRenderPassFramebuffer.setHandle(framebuffer.getHandle());
        mRenderPassRenderArea = renderArea;
        std::copy(clearValues.begin(), clearValues.end(), mRenderPassClearValues.begin());
    }
    
    // static
    void CommandGraphNode::SetHappensBeforeDependencies(CommandGraphNode **beforeNodes,
                                                        size_t beforeNodesCount,
                                                        CommandGraphNode *afterNode)
    {
        afterNode->mParents.insert(afterNode->mParents.end(), beforeNodes,
                                   beforeNodes + beforeNodesCount);
    
        // TODO(jmadill): is there a faster way to do this?
        for (size_t i = 0; i < beforeNodesCount; ++i)
        {
            beforeNodes[i]->setHasChildren();
    
            ASSERT(beforeNodes[i] != afterNode && !beforeNodes[i]->isChildOf(afterNode));
        }
    }
    
    void CommandGraphNode::SetHappensBeforeDependencies(CommandGraphNode *beforeNode,
                                                        CommandGraphNode **afterNodes,
                                                        size_t afterNodesCount)
    {
        for (size_t i = 0; i < afterNodesCount; ++i)
        {
            SetHappensBeforeDependency(beforeNode, afterNodes[i]);
        }
    }
    
    bool CommandGraphNode::hasParents() const
    {
        return !mParents.empty();
    }
    
    void CommandGraphNode::setQueryPool(const QueryPool *queryPool, uint32_t queryIndex)
    {
        ASSERT(mFunction == CommandGraphNodeFunction::BeginQuery ||
               mFunction == CommandGraphNodeFunction::EndQuery ||
               mFunction == CommandGraphNodeFunction::WriteTimestamp ||
               mFunction == CommandGraphNodeFunction::BeginTransformFeedbackQuery ||
               mFunction == CommandGraphNodeFunction::EndTransformFeedbackQuery);
        mQueryPool  = queryPool->getHandle();
        mQueryIndex = queryIndex;
    }
    
    void CommandGraphNode::setFenceSync(const vk::Event &event)
    {
        ASSERT(mFunction == CommandGraphNodeFunction::SetFenceSync ||
               mFunction == CommandGraphNodeFunction::WaitFenceSync);
        mFenceSyncEvent = event.getHandle();
    }
    
    void CommandGraphNode::setDebugMarker(GLenum source, std::string &&marker)
    {
        ASSERT(mFunction == CommandGraphNodeFunction::InsertDebugMarker ||
               mFunction == CommandGraphNodeFunction::PushDebugMarker);
        mDebugMarkerSource = source;
        mDebugMarker       = std::move(marker);
    }
    
    // Do not call this in anything but testing code, since it's slow.
    bool CommandGraphNode::isChildOf(CommandGraphNode *parent)
    {
        std::set<CommandGraphNode *> visitedList;
        std::vector<CommandGraphNode *> openList;
        openList.insert(openList.begin(), mParents.begin(), mParents.end());
        while (!openList.empty())
        {
            CommandGraphNode *current = openList.back();
            openList.pop_back();
            if (visitedList.count(current) == 0)
            {
                if (current == parent)
                {
                    return true;
                }
                visitedList.insert(current);
                openList.insert(openList.end(), current->mParents.begin(), current->mParents.end());
            }
        }
    
        return false;
    }
    
    VisitedState CommandGraphNode::visitedState() const
    {
        return mVisitedState;
    }
    
    void CommandGraphNode::visitParents(std::vector<CommandGraphNode *> *stack)
    {
        ASSERT(mVisitedState == VisitedState::Unvisited);
        stack->insert(stack->end(), mParents.begin(), mParents.end());
        mVisitedState = VisitedState::Ready;
    }
    
    angle::Result CommandGraphNode::visitAndExecute(vk::Context *context,
                                                    Serial serial,
                                                    RenderPassCache *renderPassCache,
                                                    PrimaryCommandBuffer *primaryCommandBuffer)
    {
        // Record the deferred pipeline barrier if necessary.
        ASSERT((mGlobalMemoryBarrierDstAccess == 0) == (mGlobalMemoryBarrierSrcAccess == 0));
        if (mGlobalMemoryBarrierSrcAccess)
        {
            VkMemoryBarrier memoryBarrier = {};
            memoryBarrier.sType           = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
            memoryBarrier.srcAccessMask   = mGlobalMemoryBarrierSrcAccess;
            memoryBarrier.dstAccessMask   = mGlobalMemoryBarrierDstAccess;
    
            primaryCommandBuffer->memoryBarrier(mGlobalMemoryBarrierStages, mGlobalMemoryBarrierStages,
                                                &memoryBarrier);
        }
    
        switch (mFunction)
        {
            case CommandGraphNodeFunction::Generic:
                ASSERT(mQueryPool == VK_NULL_HANDLE && mFenceSyncEvent == VK_NULL_HANDLE);
    
                if (mOutsideRenderPassCommands.valid())
                {
                    ANGLE_VK_TRY(context, mOutsideRenderPassCommands.end());
                    ExecuteCommands(primaryCommandBuffer, &mOutsideRenderPassCommands);
                }
    
                if (mInsideRenderPassCommands.valid())
                {
                    // Pull a RenderPass from the cache.
                    // TODO(jmadill): Insert layout transitions.
                    RenderPass *renderPass = nullptr;
                    ANGLE_TRY(renderPassCache->getRenderPassWithOps(
                        context, serial, mRenderPassDesc, mRenderPassAttachmentOps, &renderPass));
    
                    ANGLE_VK_TRY(context, mInsideRenderPassCommands.end());
    
                    VkRenderPassBeginInfo beginInfo = {};
                    beginInfo.sType                 = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
                    beginInfo.renderPass            = renderPass->getHandle();
                    beginInfo.framebuffer           = mRenderPassFramebuffer.getHandle();
                    beginInfo.renderArea.offset.x   = static_cast<uint32_t>(mRenderPassRenderArea.x);
                    beginInfo.renderArea.offset.y   = static_cast<uint32_t>(mRenderPassRenderArea.y);
                    beginInfo.renderArea.extent.width =
                        static_cast<uint32_t>(mRenderPassRenderArea.width);
                    beginInfo.renderArea.extent.height =
                        static_cast<uint32_t>(mRenderPassRenderArea.height);
                    beginInfo.clearValueCount =
                        static_cast<uint32_t>(mRenderPassDesc.attachmentCount());
                    beginInfo.pClearValues = mRenderPassClearValues.data();
    
                    primaryCommandBuffer->beginRenderPass(beginInfo, kRenderPassContents);
                    ExecuteCommands(primaryCommandBuffer, &mInsideRenderPassCommands);
                    primaryCommandBuffer->endRenderPass();
                }
                break;
    
            case CommandGraphNodeFunction::BeginQuery:
                ASSERT(!mOutsideRenderPassCommands.valid() && !mInsideRenderPassCommands.valid());
                ASSERT(mQueryPool != VK_NULL_HANDLE);
    
                primaryCommandBuffer->resetQueryPool(mQueryPool, mQueryIndex, 1);
                primaryCommandBuffer->beginQuery(mQueryPool, mQueryIndex, 0);
    
                break;
    
            case CommandGraphNodeFunction::EndQuery:
                ASSERT(!mOutsideRenderPassCommands.valid() && !mInsideRenderPassCommands.valid());
                ASSERT(mQueryPool != VK_NULL_HANDLE);
    
                primaryCommandBuffer->endQuery(mQueryPool, mQueryIndex);
    
                break;
    
            case CommandGraphNodeFunction::WriteTimestamp:
                ASSERT(!mOutsideRenderPassCommands.valid() && !mInsideRenderPassCommands.valid());
                ASSERT(mQueryPool != VK_NULL_HANDLE);
    
                primaryCommandBuffer->resetQueryPool(mQueryPool, mQueryIndex, 1);
                primaryCommandBuffer->writeTimestamp(VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mQueryPool,
                                                     mQueryIndex);
    
                break;
    
            case CommandGraphNodeFunction::BeginTransformFeedbackQuery:
                // Unless using VK_EXT_transform_feedback (not implemented currently), there's nothing
                // to do.
                break;
    
            case CommandGraphNodeFunction::EndTransformFeedbackQuery:
                // Same as BeginTransformFeedbackQuery.
                break;
    
            case CommandGraphNodeFunction::SetFenceSync:
                ASSERT(!mOutsideRenderPassCommands.valid() && !mInsideRenderPassCommands.valid());
                ASSERT(mFenceSyncEvent != VK_NULL_HANDLE);
    
                primaryCommandBuffer->setEvent(mFenceSyncEvent, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
    
                break;
    
            case CommandGraphNodeFunction::WaitFenceSync:
                ASSERT(!mOutsideRenderPassCommands.valid() && !mInsideRenderPassCommands.valid());
                ASSERT(mFenceSyncEvent != VK_NULL_HANDLE);
    
                // Fence Syncs are purely execution barriers, so there are no memory barriers attached.
                primaryCommandBuffer->waitEvents(
                    1, &mFenceSyncEvent, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
                    VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, nullptr, 0, nullptr, 0, nullptr);
    
                break;
    
            case CommandGraphNodeFunction::GraphBarrier:
                // Nothing to do.  The memory barrier, if any, is already handled above through global
                // memory barrier flags.
                break;
    
            case CommandGraphNodeFunction::InsertDebugMarker:
                ASSERT(!mOutsideRenderPassCommands.valid() && !mInsideRenderPassCommands.valid());
    
                if (vkCmdInsertDebugUtilsLabelEXT)
                {
                    VkDebugUtilsLabelEXT label;
                    MakeDebugUtilsLabel(mDebugMarkerSource, mDebugMarker.c_str(), &label);
    
                    vkCmdInsertDebugUtilsLabelEXT(primaryCommandBuffer->getHandle(), &label);
                }
                break;
    
            case CommandGraphNodeFunction::PushDebugMarker:
                ASSERT(!mOutsideRenderPassCommands.valid() && !mInsideRenderPassCommands.valid());
    
                if (vkCmdBeginDebugUtilsLabelEXT)
                {
                    VkDebugUtilsLabelEXT label;
                    MakeDebugUtilsLabel(mDebugMarkerSource, mDebugMarker.c_str(), &label);
    
                    vkCmdBeginDebugUtilsLabelEXT(primaryCommandBuffer->getHandle(), &label);
                }
                break;
    
            case CommandGraphNodeFunction::PopDebugMarker:
                ASSERT(!mOutsideRenderPassCommands.valid() && !mInsideRenderPassCommands.valid());
    
                if (vkCmdEndDebugUtilsLabelEXT)
                {
                    vkCmdEndDebugUtilsLabelEXT(primaryCommandBuffer->getHandle());
                }
                break;
    
            case CommandGraphNodeFunction::HostAvailabilityOperation:
                // Make sure all writes to host-visible buffers are flushed.  We have no way of knowing
                // whether any buffer will be mapped for readback in the future, and we can't afford to
                // flush and wait on a one-pipeline-barrier command buffer on every map().
                {
                    VkMemoryBarrier memoryBarrier = {};
                    memoryBarrier.sType           = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
                    memoryBarrier.srcAccessMask   = VK_ACCESS_MEMORY_WRITE_BIT;
                    memoryBarrier.dstAccessMask   = VK_ACCESS_HOST_READ_BIT;
    
                    primaryCommandBuffer->memoryBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
                                                        VK_PIPELINE_STAGE_HOST_BIT, &memoryBarrier);
                }
                break;
    
            default:
                UNREACHABLE();
        }
    
        mVisitedState = VisitedState::Visited;
        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;
    }
    
    bool CommandGraphNode::hasDiagnosticID() const
    {
        // All nodes have diagnostic IDs to differentiate them except the following select few.
        return mResourceType != CommandGraphResourceType::HostAvailabilityOperation &&
               mResourceType != CommandGraphResourceType::GraphBarrier;
    }
    
    std::string CommandGraphNode::dumpCommandsForDiagnostics(const char *separator) const
    {
        std::string result;
        if (mGlobalMemoryBarrierSrcAccess != 0 || mGlobalMemoryBarrierDstAccess != 0)
        {
            result += separator;
    
            std::ostringstream out;
            out << "Memory Barrier Src: 0x" << std::hex << mGlobalMemoryBarrierSrcAccess
                << " &rarr; Dst: 0x" << std::hex << mGlobalMemoryBarrierDstAccess;
            result += out.str();
        }
        if (mOutsideRenderPassCommands.valid())
        {
            result += separator;
            result += "Outside RP:";
            result += DumpCommands(mOutsideRenderPassCommands, separator);
        }
        if (mInsideRenderPassCommands.valid())
        {
            result += separator;
            result += "Inside RP:";
    
            size_t attachmentCount             = mRenderPassDesc.attachmentCount();
            size_t depthStencilAttachmentCount = mRenderPassDesc.hasDepthStencilAttachment();
            size_t colorAttachmentCount        = attachmentCount - depthStencilAttachmentCount;
    
            std::string loadOps, storeOps;
    
            if (colorAttachmentCount > 0)
            {
                loadOps += " Color: ";
                storeOps += " Color: ";
    
                for (size_t i = 0; i < colorAttachmentCount; ++i)
                {
                    loadOps += GetLoadOpShorthand(mRenderPassAttachmentOps[i].loadOp);
                    storeOps += GetStoreOpShorthand(mRenderPassAttachmentOps[i].storeOp);
                }
            }
    
            if (depthStencilAttachmentCount > 0)
            {
                ASSERT(depthStencilAttachmentCount == 1);
    
                loadOps += " Depth/Stencil: ";
                storeOps += " Depth/Stencil: ";
                size_t dsIndex = colorAttachmentCount;
    
                loadOps += GetLoadOpShorthand(mRenderPassAttachmentOps[dsIndex].loadOp);
                loadOps += GetLoadOpShorthand(mRenderPassAttachmentOps[dsIndex].stencilLoadOp);
    
                storeOps += GetStoreOpShorthand(mRenderPassAttachmentOps[dsIndex].storeOp);
                storeOps += GetStoreOpShorthand(mRenderPassAttachmentOps[dsIndex].stencilStoreOp);
            }
    
            if (attachmentCount > 0)
            {
                result += " LoadOp: " + loadOps;
                result += separator;
                result += "------------ StoreOp: " + storeOps;
            }
    
            result += DumpCommands(mInsideRenderPassCommands, separator);
        }
        return result;
    }
    
    void CommandGraphNode::getMemoryUsageStatsForDiagnostics(size_t *usedMemoryOut,
                                                             size_t *allocatedMemoryOut) const
    {
        size_t commandBufferUsed;
        size_t commandBufferAllocated;
    
        mOutsideRenderPassCommands.getMemoryUsageStats(usedMemoryOut, allocatedMemoryOut);
        mInsideRenderPassCommands.getMemoryUsageStats(&commandBufferUsed, &commandBufferAllocated);
    
        *usedMemoryOut += commandBufferUsed;
        *allocatedMemoryOut += commandBufferAllocated;
    }
    
    // SharedGarbage implementation.
    SharedGarbage::SharedGarbage() = default;
    
    SharedGarbage::SharedGarbage(SharedGarbage &&other)
    {
        *this = std::move(other);
    }
    
    SharedGarbage::SharedGarbage(SharedResourceUse &&use, std::vector<GarbageObject> &&garbage)
        : mLifetime(std::move(use)), mGarbage(std::move(garbage))
    {}
    
    SharedGarbage::~SharedGarbage() = default;
    
    SharedGarbage &SharedGarbage::operator=(SharedGarbage &&rhs)
    {
        std::swap(mLifetime, rhs.mLifetime);
        std::swap(mGarbage, rhs.mGarbage);
        return *this;
    }
    
    bool SharedGarbage::destroyIfComplete(VkDevice device, Serial completedSerial)
    {
        if (mLifetime.isCurrentlyInGraph() || mLifetime.getSerial() > completedSerial)
            return false;
    
        mLifetime.release();
    
        for (GarbageObject &object : mGarbage)
        {
            object.destroy(device);
        }
    
        return true;
    }
    
    // CommandGraph implementation.
    CommandGraph::CommandGraph(bool enableGraphDiagnostics, angle::PoolAllocator *poolAllocator)
        : mEnableGraphDiagnostics(enableGraphDiagnostics),
          mPoolAllocator(poolAllocator),
          mLastBarrierIndex(kInvalidNodeIndex)
    {
        // Push so that allocations made from here will be recycled in clear() below.
        mPoolAllocator->push();
    }
    
    CommandGraph::~CommandGraph()
    {
        ASSERT(empty());
        ASSERT(mResourceUses.empty());
    }
    
    CommandGraphNode *CommandGraph::allocateNode(CommandGraphNodeFunction function)
    {
        // TODO(jmadill): Use a pool allocator for the CPU node allocations.
        CommandGraphNode *newCommands = new CommandGraphNode(function, mPoolAllocator);
        mNodes.emplace_back(newCommands);
        return newCommands;
    }
    
    CommandGraphNode *CommandGraph::allocateBarrierNode(CommandGraphNodeFunction function,
                                                        CommandGraphResourceType resourceType,
                                                        uintptr_t resourceID)
    {
        CommandGraphNode *newNode = allocateNode(function);
        newNode->setDiagnosticInfo(resourceType, resourceID);
        setNewBarrier(newNode);
    
        return newNode;
    }
    
    void CommandGraph::setNewBarrier(CommandGraphNode *newBarrier)
    {
        size_t previousBarrierIndex       = 0;
        CommandGraphNode *previousBarrier = getLastBarrierNode(&previousBarrierIndex);
    
        // Add a dependency from previousBarrier to all nodes in (previousBarrier, newBarrier).
        if (previousBarrier && previousBarrierIndex + 1 < mNodes.size())
        {
            size_t afterNodesCount = mNodes.size() - (previousBarrierIndex + 2);
            CommandGraphNode::SetHappensBeforeDependencies(
                previousBarrier, &mNodes[previousBarrierIndex + 1], afterNodesCount);
        }
    
        // Add a dependency from all nodes in [previousBarrier, newBarrier) to newBarrier.
        addDependenciesToNextBarrier(previousBarrierIndex, mNodes.size() - 1, newBarrier);
    
        mLastBarrierIndex = mNodes.size() - 1;
    }
    
    angle::Result CommandGraph::submitCommands(ContextVk *context,
                                               Serial serial,
                                               RenderPassCache *renderPassCache,
                                               PrimaryCommandBuffer *primaryCommandBuffer)
    {
        // There is no point in submitting an empty command buffer, so make sure not to call this
        // function if there's nothing to do.
        ASSERT(!mNodes.empty());
    
        updateOverlay(context);
    
        size_t previousBarrierIndex       = 0;
        CommandGraphNode *previousBarrier = getLastBarrierNode(&previousBarrierIndex);
    
        // Add a dependency from previousBarrier to all nodes in (previousBarrier, end].
        if (previousBarrier && previousBarrierIndex + 1 < mNodes.size())
        {
            size_t afterNodesCount = mNodes.size() - (previousBarrierIndex + 1);
            CommandGraphNode::SetHappensBeforeDependencies(
                previousBarrier, &mNodes[previousBarrierIndex + 1], afterNodesCount);
        }
    
        if (mEnableGraphDiagnostics)
        {
            dumpGraphDotFile(std::cout);
        }
    
        releaseResourceUsesAndUpdateSerials(serial);
    
        std::vector<CommandGraphNode *> nodeStack;
    
        VkCommandBufferBeginInfo beginInfo = {};
        beginInfo.sType                    = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
        beginInfo.flags                    = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
        beginInfo.pInheritanceInfo         = nullptr;
    
        ANGLE_VK_TRY(context, primaryCommandBuffer->begin(beginInfo));
    
        ANGLE_TRY(context->traceGpuEvent(primaryCommandBuffer, TRACE_EVENT_PHASE_BEGIN,
                                         "Primary Command Buffer"));
    
        for (CommandGraphNode *topLevelNode : mNodes)
        {
            // Only process commands that don't have child commands. The others will be pulled in
            // automatically. Also skip commands that have already been visited.
            if (topLevelNode->hasChildren() || topLevelNode->visitedState() != VisitedState::Unvisited)
                continue;
    
            nodeStack.push_back(topLevelNode);
    
            while (!nodeStack.empty())
            {
                CommandGraphNode *node = nodeStack.back();
    
                switch (node->visitedState())
                {
                    case VisitedState::Unvisited:
                        node->visitParents(&nodeStack);
                        break;
                    case VisitedState::Ready:
                        ANGLE_TRY(node->visitAndExecute(context, serial, renderPassCache,
                                                        primaryCommandBuffer));
                        nodeStack.pop_back();
                        break;
                    case VisitedState::Visited:
                        nodeStack.pop_back();
                        break;
                    default:
                        UNREACHABLE();
                        break;
                }
            }
        }
    
        ANGLE_TRY(context->traceGpuEvent(primaryCommandBuffer, TRACE_EVENT_PHASE_END,
                                         "Primary Command Buffer"));
    
        ANGLE_VK_TRY(context, primaryCommandBuffer->end());
    
        clear();
    
        return angle::Result::Continue;
    }
    
    bool CommandGraph::empty() const
    {
        return mNodes.empty();
    }
    
    void CommandGraph::clear()
    {
        mLastBarrierIndex = kInvalidNodeIndex;
        // Release cmd graph pool memory now that cmds are submitted
        // NOTE: This frees all memory since last push. Right now only the CommandGraph
        //  will push the allocator (at creation and below). If other people start
        //  pushing the allocator this (and/or the allocator) will need to be updated.
        mPoolAllocator->pop();
        mPoolAllocator->push();
    
        // TODO(jmadill): Use pool allocator for performance. http://anglebug.com/2951
        for (CommandGraphNode *node : mNodes)
        {
            delete node;
        }
        mNodes.clear();
    }
    
    void CommandGraph::beginQuery(const QueryPool *queryPool, uint32_t queryIndex)
    {
        CommandGraphNode *newNode = allocateBarrierNode(CommandGraphNodeFunction::BeginQuery,
                                                        CommandGraphResourceType::Query, 0);
        newNode->setQueryPool(queryPool, queryIndex);
    }
    
    void CommandGraph::endQuery(const QueryPool *queryPool, uint32_t queryIndex)
    {
        CommandGraphNode *newNode =
            allocateBarrierNode(CommandGraphNodeFunction::EndQuery, CommandGraphResourceType::Query, 0);
        newNode->setQueryPool(queryPool, queryIndex);
    }
    
    void CommandGraph::writeTimestamp(const QueryPool *queryPool, uint32_t queryIndex)
    {
        CommandGraphNode *newNode = allocateBarrierNode(CommandGraphNodeFunction::WriteTimestamp,
                                                        CommandGraphResourceType::Query, 0);
        newNode->setQueryPool(queryPool, queryIndex);
    }
    
    void CommandGraph::beginTransformFeedbackEmulatedQuery()
    {
        allocateBarrierNode(CommandGraphNodeFunction::BeginTransformFeedbackQuery,
                            CommandGraphResourceType::EmulatedQuery, 0);
    }
    
    void CommandGraph::endTransformFeedbackEmulatedQuery()
    {
        allocateBarrierNode(CommandGraphNodeFunction::EndTransformFeedbackQuery,
                            CommandGraphResourceType::EmulatedQuery, 0);
    }
    
    void CommandGraph::setFenceSync(const vk::Event &event)
    {
        CommandGraphNode *newNode = allocateBarrierNode(CommandGraphNodeFunction::SetFenceSync,
                                                        CommandGraphResourceType::FenceSync,
                                                        reinterpret_cast<uintptr_t>(&event));
        newNode->setFenceSync(event);
    }
    
    void CommandGraph::waitFenceSync(const vk::Event &event)
    {
        CommandGraphNode *newNode = allocateBarrierNode(CommandGraphNodeFunction::WaitFenceSync,
                                                        CommandGraphResourceType::FenceSync,
                                                        reinterpret_cast<uintptr_t>(&event));
        newNode->setFenceSync(event);
    }
    
    void CommandGraph::memoryBarrier(VkFlags srcAccess, VkFlags dstAccess, VkPipelineStageFlags stages)
    {
        CommandGraphNode *newNode = allocateBarrierNode(CommandGraphNodeFunction::GraphBarrier,
                                                        CommandGraphResourceType::GraphBarrier, 0);
        newNode->addGlobalMemoryBarrier(srcAccess, dstAccess, stages);
    }
    
    void CommandGraph::insertDebugMarker(GLenum source, std::string &&marker)
    {
        CommandGraphNode *newNode = allocateBarrierNode(CommandGraphNodeFunction::InsertDebugMarker,
                                                        CommandGraphResourceType::DebugMarker, 0);
        newNode->setDebugMarker(source, std::move(marker));
    }
    
    void CommandGraph::pushDebugMarker(GLenum source, std::string &&marker)
    {
        CommandGraphNode *newNode = allocateBarrierNode(CommandGraphNodeFunction::PushDebugMarker,
                                                        CommandGraphResourceType::DebugMarker, 0);
        newNode->setDebugMarker(source, std::move(marker));
    }
    
    void CommandGraph::popDebugMarker()
    {
        allocateBarrierNode(CommandGraphNodeFunction::PopDebugMarker,
                            CommandGraphResourceType::DebugMarker, 0);
    }
    
    void CommandGraph::makeHostVisibleBufferWriteAvailable()
    {
        allocateBarrierNode(CommandGraphNodeFunction::HostAvailabilityOperation,
                            CommandGraphResourceType::HostAvailabilityOperation, 0);
    }
    
    // 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 monotonic ID. It allows us to look up parent node IDs.
        std::map<const CommandGraphNode *, int> nodeIDMap;
        std::map<uintptr_t, int> objectIDMap;
        std::map<std::pair<VkQueryPool, uint32_t>, int> queryIDMap;
    
        // 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;
        int queryIDCounter       = 1;
        int dispatcherIDCounter  = 1;
        int fenceIDCounter       = 1;
        int xfbIDCounter         = 1;
    
        out << "digraph {" << std::endl;
    
        for (const CommandGraphNode *node : mNodes)
        {
            int nodeID = nodeIDMap[node];
    
            std::stringstream strstr;
            strstr << GetResourceTypeName(node->getResourceTypeForDiagnostics(), node->getFunction());
    
            if (node->getResourceTypeForDiagnostics() == CommandGraphResourceType::DebugMarker)
            {
                // For debug markers, use the string from the debug marker itself.
                if (node->getFunction() != CommandGraphNodeFunction::PopDebugMarker)
                {
                    strstr << " " << node->getDebugMarker();
                }
            }
            else if (node->getResourceTypeForDiagnostics() == CommandGraphResourceType::Query)
            {
                // Special case for queries as they cannot generate a resource ID at creation time that
                // would reliably fit in a uintptr_t.
                strstr << " ";
    
                ASSERT(node->getResourceIDForDiagnostics() == 0);
    
                auto queryID = std::make_pair(node->getQueryPool(), node->getQueryIndex());
    
                auto it = queryIDMap.find(queryID);
                if (it != queryIDMap.end())
                {
                    strstr << it->second;
                }
                else
                {
                    int id = queryIDCounter++;
    
                    queryIDMap[queryID] = id;
                    strstr << id;
                }
            }
            else if (!node->hasDiagnosticID())
            {
                // Nothing to append for these special nodes.  The name is sufficient.
            }
            else
            {
                strstr << " ";
    
                // Otherwise assign each object an ID, so all the nodes of the same object have the same
                // label.
                ASSERT(node->getResourceIDForDiagnostics() != 0);
                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;
                        case CommandGraphResourceType::Dispatcher:
                            id = dispatcherIDCounter++;
                            break;
                        case CommandGraphResourceType::FenceSync:
                            id = fenceIDCounter++;
                            break;
                        case CommandGraphResourceType::EmulatedQuery:
                            id = xfbIDCounter++;
                            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 << node->dumpCommandsForDiagnostics("<BR/>") << "</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;
    }
    
    void CommandGraph::updateOverlay(ContextVk *contextVk) const
    {
        const gl::OverlayType *overlay = contextVk->getOverlay();
    
        overlay->getRunningGraphWidget(gl::WidgetId::VulkanCommandGraphSize)->add(mNodes.size());
    
        overlay->getRunningHistogramWidget(gl::WidgetId::VulkanSecondaryCommandBufferPoolWaste)
            ->set(CalculateSecondaryCommandBufferPoolWaste(mNodes));
        overlay->getRunningHistogramWidget(gl::WidgetId::VulkanSecondaryCommandBufferPoolWaste)->next();
    }
    
    CommandGraphNode *CommandGraph::getLastBarrierNode(size_t *indexOut)
    {
        *indexOut = mLastBarrierIndex == kInvalidNodeIndex ? 0 : mLastBarrierIndex;
        return mLastBarrierIndex == kInvalidNodeIndex ? nullptr : mNodes[mLastBarrierIndex];
    }
    
    void CommandGraph::addDependenciesToNextBarrier(size_t begin,
                                                    size_t end,
                                                    CommandGraphNode *nextBarrier)
    {
        for (size_t i = begin; i < end; ++i)
        {
            // As a small optimization, only add edges to childless nodes.  The others have an
            // indirect dependency.
            if (!mNodes[i]->hasChildren())
            {
                CommandGraphNode::SetHappensBeforeDependency(mNodes[i], nextBarrier);
            }
        }
    }
    
    void CommandGraph::releaseResourceUses()
    {
        for (SharedResourceUse &use : mResourceUses)
        {
            use.release();
        }
    
        mResourceUses.clear();
    }
    
    void CommandGraph::releaseResourceUsesAndUpdateSerials(Serial serial)
    {
        for (SharedResourceUse &use : mResourceUses)
        {
            use.releaseAndUpdateSerial(serial);
        }
    
        mResourceUses.clear();
    }
    }  // namespace vk
    }  // namespace rx