Edit

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

Branch :

  • Show log

    Commit

  • Author : Jamie Madill
    Date : 2019-10-13 15:05:23
    Hash : 68591eff
    Message : Vulkan: Store ImageView access in the graph. This will ensure we don't destroy the image views when they are still in use by other Contexts. Bug: angleproject:2464 Change-Id: I1d3ba2ad241250e31ea32873446c4cb23971750d Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1843236 Reviewed-by: Tim Van Patten <timvp@google.com> Commit-Queue: Jamie Madill <jmadill@chromium.org>

  • src/libANGLE/renderer/vulkan/CommandGraph.h
  • //
    // 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.
    //
    
    #ifndef LIBANGLE_RENDERER_VULKAN_COMMAND_GRAPH_H_
    #define LIBANGLE_RENDERER_VULKAN_COMMAND_GRAPH_H_
    
    #include "libANGLE/renderer/vulkan/SecondaryCommandBuffer.h"
    #include "libANGLE/renderer/vulkan/vk_cache_utils.h"
    
    namespace rx
    {
    
    namespace vk
    {
    class CommandGraph;
    
    enum class VisitedState
    {
        Unvisited,
        Ready,
        Visited,
    };
    
    enum class CommandGraphResourceType
    {
        Buffer,
        Framebuffer,
        Image,
        Query,
        Dispatcher,
        // Transform feedback queries could be handled entirely on the CPU (if not using
        // VK_EXT_transform_feedback), but still need to generate a command graph barrier node.
        EmulatedQuery,
        FenceSync,
        GraphBarrier,
        DebugMarker,
        HostAvailabilityOperation,
    };
    
    // Certain functionality cannot be put in secondary command buffers, so they are special-cased in
    // the node.
    enum class CommandGraphNodeFunction
    {
        Generic,
        BeginQuery,
        EndQuery,
        WriteTimestamp,
        BeginTransformFeedbackQuery,
        EndTransformFeedbackQuery,
        SetFenceSync,
        WaitFenceSync,
        GraphBarrier,
        InsertDebugMarker,
        PushDebugMarker,
        PopDebugMarker,
        HostAvailabilityOperation,
    };
    
    // Receives notifications when a render pass command buffer is no longer able to record. Can be
    // used with inheritance. Faster than using an interface class since it has inlined methods. Could
    // be used with composition by adding a getCommandBuffer method.
    class RenderPassOwner
    {
      public:
        RenderPassOwner() = default;
        virtual ~RenderPassOwner() {}
    
        ANGLE_INLINE void onRenderPassFinished() { mRenderPassCommandBuffer = nullptr; }
    
      protected:
        CommandBuffer *mRenderPassCommandBuffer = nullptr;
    };
    
    // Only used internally in the command graph. Kept in the header for better inlining performance.
    class CommandGraphNode final : angle::NonCopyable
    {
      public:
        CommandGraphNode(CommandGraphNodeFunction function, angle::PoolAllocator *poolAllocator);
        ~CommandGraphNode();
    
        // Immutable queries for when we're walking the commands tree.
        CommandBuffer *getOutsideRenderPassCommands()
        {
            ASSERT(!mHasChildren);
            return &mOutsideRenderPassCommands;
        }
    
        CommandBuffer *getInsideRenderPassCommands()
        {
            ASSERT(!mHasChildren);
            return &mInsideRenderPassCommands;
        }
    
        // For outside the render pass (copies, transitions, etc).
        angle::Result beginOutsideRenderPassRecording(ContextVk *context,
                                                      const CommandPool &commandPool,
                                                      CommandBuffer **commandsOut);
    
        // For rendering commands (draws).
        angle::Result beginInsideRenderPassRecording(ContextVk *context, CommandBuffer **commandsOut);
    
        // storeRenderPassInfo and append*RenderTarget store info relevant to the RenderPass.
        void storeRenderPassInfo(const Framebuffer &framebuffer,
                                 const gl::Rectangle renderArea,
                                 const vk::RenderPassDesc &renderPassDesc,
                                 const AttachmentOpsArray &renderPassAttachmentOps,
                                 const std::vector<VkClearValue> &clearValues);
    
        void clearRenderPassColorAttachment(size_t attachmentIndex, const VkClearColorValue &clearValue)
        {
            mRenderPassAttachmentOps[attachmentIndex].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
            mRenderPassClearValues[attachmentIndex].color    = clearValue;
        }
    
        void clearRenderPassDepthAttachment(size_t attachmentIndex, float depth)
        {
            mRenderPassAttachmentOps[attachmentIndex].loadOp           = VK_ATTACHMENT_LOAD_OP_CLEAR;
            mRenderPassClearValues[attachmentIndex].depthStencil.depth = depth;
        }
    
        void clearRenderPassStencilAttachment(size_t attachmentIndex, uint32_t stencil)
        {
            mRenderPassAttachmentOps[attachmentIndex].stencilLoadOp      = VK_ATTACHMENT_LOAD_OP_CLEAR;
            mRenderPassClearValues[attachmentIndex].depthStencil.stencil = stencil;
        }
    
        void invalidateRenderPassColorAttachment(size_t attachmentIndex)
        {
            mRenderPassAttachmentOps[attachmentIndex].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
        }
    
        void invalidateRenderPassDepthAttachment(size_t attachmentIndex)
        {
            mRenderPassAttachmentOps[attachmentIndex].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
        }
    
        void invalidateRenderPassStencilAttachment(size_t attachmentIndex)
        {
            mRenderPassAttachmentOps[attachmentIndex].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
        }
    
        // Dependency commands order node execution in the command graph.
        // Once a node has commands that must happen after it, recording is stopped and the node is
        // frozen forever.
        static void SetHappensBeforeDependency(CommandGraphNode *beforeNode,
                                               CommandGraphNode *afterNode)
        {
            ASSERT(beforeNode != afterNode && !beforeNode->isChildOf(afterNode));
            afterNode->mParents.emplace_back(beforeNode);
            beforeNode->setHasChildren();
        }
    
        static void SetHappensBeforeDependencies(CommandGraphNode **beforeNodes,
                                                 size_t beforeNodesCount,
                                                 CommandGraphNode *afterNode);
    
        static void SetHappensBeforeDependencies(CommandGraphNode *beforeNode,
                                                 CommandGraphNode **afterNodes,
                                                 size_t afterNodesCount);
    
        bool hasParents() const;
        bool hasChildren() const { return mHasChildren; }
    
        // Commands for traversing the node on a flush operation.
        VisitedState visitedState() const;
        void visitParents(std::vector<CommandGraphNode *> *stack);
        angle::Result visitAndExecute(Context *context,
                                      Serial serial,
                                      RenderPassCache *renderPassCache,
                                      PrimaryCommandBuffer *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; }
        bool hasDiagnosticID() const;
        std::string dumpCommandsForDiagnostics(const char *separator) const;
        void getMemoryUsageStatsForDiagnostics(size_t *usedMemoryOut, size_t *allocatedMemoryOut) const;
    
        const gl::Rectangle &getRenderPassRenderArea() const { return mRenderPassRenderArea; }
    
        CommandGraphNodeFunction getFunction() const { return mFunction; }
    
        void setQueryPool(const QueryPool *queryPool, uint32_t queryIndex);
        VkQueryPool getQueryPool() const { return mQueryPool; }
        uint32_t getQueryIndex() const { return mQueryIndex; }
        void setFenceSync(const vk::Event &event);
        void setDebugMarker(GLenum source, std::string &&marker);
        const std::string &getDebugMarker() const { return mDebugMarker; }
    
        ANGLE_INLINE void addGlobalMemoryBarrier(VkFlags srcAccess,
                                                 VkFlags dstAccess,
                                                 VkPipelineStageFlags stages)
        {
            mGlobalMemoryBarrierSrcAccess |= srcAccess;
            mGlobalMemoryBarrierDstAccess |= dstAccess;
            mGlobalMemoryBarrierStages |= stages;
        }
    
        // This can only be set for RenderPass nodes. Each RenderPass node can have at most one owner.
        void setRenderPassOwner(RenderPassOwner *owner)
        {
            ASSERT(mRenderPassOwner == nullptr);
            mRenderPassOwner = owner;
        }
    
      private:
        ANGLE_INLINE void setHasChildren()
        {
            mHasChildren = true;
            if (mRenderPassOwner)
            {
                mRenderPassOwner->onRenderPassFinished();
            }
        }
    
        // Used for testing only.
        bool isChildOf(CommandGraphNode *parent);
    
        // Only used if we need a RenderPass for these commands.
        RenderPassDesc mRenderPassDesc;
        AttachmentOpsArray mRenderPassAttachmentOps;
        Framebuffer mRenderPassFramebuffer;
        gl::Rectangle mRenderPassRenderArea;
        gl::AttachmentArray<VkClearValue> mRenderPassClearValues;
    
        CommandGraphNodeFunction mFunction;
        angle::PoolAllocator *mPoolAllocator;
        // Keep separate buffers for commands inside and outside a RenderPass.
        // TODO(jmadill): We might not need inside and outside RenderPass commands separate.
        CommandBuffer mOutsideRenderPassCommands;
        CommandBuffer mInsideRenderPassCommands;
    
        // Special-function additional data:
        // Queries:
        VkQueryPool mQueryPool;
        uint32_t mQueryIndex;
        // GLsync and EGLSync:
        VkEvent mFenceSyncEvent;
        // Debug markers:
        GLenum mDebugMarkerSource;
        std::string mDebugMarker;
    
        // Parents are commands that must be submitted before 'this' CommandNode can be submitted.
        std::vector<CommandGraphNode *> mParents;
    
        // If this is true, other commands exist that must be submitted after 'this' command.
        bool mHasChildren;
    
        // Used when traversing the dependency graph.
        VisitedState mVisitedState;
    
        // Additional diagnostic information.
        CommandGraphResourceType mResourceType;
        uintptr_t mResourceID;
    
        // For global memory barriers.
        VkFlags mGlobalMemoryBarrierSrcAccess;
        VkFlags mGlobalMemoryBarrierDstAccess;
        VkPipelineStageFlags mGlobalMemoryBarrierStages;
    
        // Render pass command buffer notifications.
        RenderPassOwner *mRenderPassOwner;
    };
    
    // Tracks how a resource is used in a command graph and in a VkQueue. The reference count indicates
    // the number of times a resource is used in the graph. The serial indicates the last current use
    // of a resource in the VkQueue. The reference count and serial together can determine if a
    // resource is in use.
    struct ResourceUse
    {
        ResourceUse() = default;
    
        uint32_t counter = 0;
        Serial serial;
    };
    
    class SharedResourceUse final : angle::NonCopyable
    {
      public:
        SharedResourceUse() : mUse(nullptr) {}
        ~SharedResourceUse() { ASSERT(!valid()); }
        SharedResourceUse(SharedResourceUse &&rhs) : mUse(rhs.mUse) { rhs.mUse = nullptr; }
        SharedResourceUse &operator=(SharedResourceUse &&rhs)
        {
            std::swap(mUse, rhs.mUse);
            return *this;
        }
    
        ANGLE_INLINE bool valid() const { return mUse != nullptr; }
    
        void init()
        {
            ASSERT(!mUse);
            mUse = new ResourceUse;
            mUse->counter++;
        }
    
        ANGLE_INLINE void release()
        {
            ASSERT(valid());
            ASSERT(mUse->counter > 0);
            if (--mUse->counter == 0)
            {
                delete mUse;
            }
            mUse = nullptr;
        }
    
        ANGLE_INLINE void releaseAndUpdateSerial(Serial serial)
        {
            ASSERT(valid());
            ASSERT(mUse->counter > 0);
            ASSERT(mUse->serial <= serial);
            mUse->serial = serial;
            release();
        }
    
        ANGLE_INLINE void set(const SharedResourceUse &rhs)
        {
            ASSERT(rhs.valid());
            ASSERT(!valid());
            ASSERT(rhs.mUse->counter < std::numeric_limits<uint32_t>::max());
            mUse = rhs.mUse;
            mUse->counter++;
        }
    
        ANGLE_INLINE Serial getSerial() const
        {
            ASSERT(valid());
            return mUse->serial;
        }
    
        // The base counter value for an live resource is "1". Any value greater than one indicates
        // the resource is in use by a vk::CommandGraph.
        ANGLE_INLINE bool isCurrentlyInGraph() const
        {
            ASSERT(valid());
            return mUse->counter > 1;
        }
    
      private:
        ResourceUse *mUse;
    };
    
    class SharedGarbage
    {
      public:
        SharedGarbage();
        SharedGarbage(SharedGarbage &&other);
        SharedGarbage(SharedResourceUse &&use, std::vector<GarbageObject> &&garbage);
        ~SharedGarbage();
        SharedGarbage &operator=(SharedGarbage &&rhs);
    
        bool destroyIfComplete(VkDevice device, Serial completedSerial);
    
      private:
        SharedResourceUse mLifetime;
        std::vector<GarbageObject> mGarbage;
    };
    
    using SharedGarbageList = std::vector<SharedGarbage>;
    
    // This is a helper class for back-end objects used in Vk command buffers. It records a serial
    // at command recording times indicating an order in the queue. We use Fences to detect when
    // commands finish, and then release any unreferenced and deleted resources based on the stored
    // 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 : angle::NonCopyable
    {
      public:
        virtual ~CommandGraphResource();
    
        // Returns true if the resource is in use by the renderer.
        bool isResourceInUse(ContextVk *contextVk) const;
    
        // queries, to know if the queue they are submitted on has finished execution.
        Serial getLatestSerial() const { return mUse.getSerial(); }
    
        // Sets up dependency relations. 'this' resource is the resource being written to.
        void addWriteDependency(ContextVk *contextVk, CommandGraphResource *writingResource);
    
        // Sets up dependency relations. 'this' resource is the resource being read.
        void addReadDependency(ContextVk *contextVk, CommandGraphResource *readingResource);
    
        // Updates the in-use serial tracked for this resource. Will clear dependencies if the resource
        // was not used in this set of command nodes.
        void onGraphAccess(CommandGraph *commandGraph);
        void updateCurrentAccessNodes();
    
        // Allocates a write node via getNewWriteNode and returns a started command buffer.
        // The started command buffer will render outside of a RenderPass.
        // Will append to an existing command buffer/graph node if possible.
        angle::Result recordCommands(ContextVk *context, CommandBuffer **commandBufferOut);
    
        // Begins a command buffer on the current graph node for in-RenderPass rendering.
        // Called from FramebufferVk::startNewRenderPass and UtilsVk functions.
        angle::Result beginRenderPass(ContextVk *contextVk,
                                      const Framebuffer &framebuffer,
                                      const gl::Rectangle &renderArea,
                                      const RenderPassDesc &renderPassDesc,
                                      const AttachmentOpsArray &renderPassAttachmentOps,
                                      const std::vector<VkClearValue> &clearValues,
                                      CommandBuffer **commandBufferOut);
    
        // Checks if we're in a RenderPass without children.
        bool hasStartedRenderPass() const;
    
        // Checks if we're in a RenderPass that encompasses renderArea, returning true if so. Updates
        // serial internally. Returns the started command buffer in commandBufferOut.
        bool appendToStartedRenderPass(CommandGraph *graph,
                                       const gl::Rectangle &renderArea,
                                       CommandBuffer **commandBufferOut);
    
        // Returns true if the render pass is started, but there are no commands yet recorded in it.
        // This is useful to know if the render pass ops can be modified.
        bool renderPassStartedButEmpty() const;
    
        void clearRenderPassColorAttachment(size_t attachmentIndex,
                                            const VkClearColorValue &clearValue);
        void clearRenderPassDepthAttachment(size_t attachmentIndex, float depth);
        void clearRenderPassStencilAttachment(size_t attachmentIndex, uint32_t stencil);
    
        void invalidateRenderPassColorAttachment(size_t attachmentIndex);
        void invalidateRenderPassDepthAttachment(size_t attachmentIndex);
        void invalidateRenderPassStencilAttachment(size_t attachmentIndex);
    
        // Accessor for RenderPass RenderArea.
        const gl::Rectangle &getRenderPassRenderArea() const;
    
        // Called when 'this' object changes, but we'd like to start a new command buffer later.
        void finishCurrentCommands(ContextVk *contextVk);
    
        // Store a deferred memory barrier. Will be recorded into a primary command buffer at submit.
        void addGlobalMemoryBarrier(VkFlags srcAccess, VkFlags dstAccess, VkPipelineStageFlags stages);
    
      protected:
        explicit CommandGraphResource(CommandGraphResourceType resourceType);
    
        // Current resource lifetime.
        SharedResourceUse mUse;
    
      private:
        // Returns true if this node has a current writing node with no children.
        ANGLE_INLINE bool hasChildlessWritingNode() const;
    
        void startNewCommands(ContextVk *contextVk);
    
        void onWriteImpl(ContextVk *contextVk, CommandGraphNode *writingNode);
    
        std::vector<CommandGraphNode *> mCurrentReadingNodes;
    
        // Current command graph writing node.
        CommandGraphNode *mCurrentWritingNode;
    
        // Additional diagnostic information.
        CommandGraphResourceType mResourceType;
    };
    
    // Translating OpenGL commands into Vulkan and submitting them immediately loses out on some
    // of the powerful flexiblity Vulkan offers in RenderPasses. Load/Store ops can automatically
    // clear RenderPass attachments, or preserve the contents. RenderPass automatic layout transitions
    // can improve certain performance cases. Also, we can remove redundant RenderPass Begin and Ends
    // when processing interleaved draw operations on independent Framebuffers.
    //
    // ANGLE's CommandGraph (and CommandGraphNode) attempt to solve these problems using deferred
    // command submission. We also sometimes call this command re-ordering. A brief summary:
    //
    // During GL command processing, we record Vulkan commands into SecondaryCommandBuffers, which
    // are stored in CommandGraphNodes, and these nodes are chained together via dependencies to
    // form a directed acyclic CommandGraph. When we need to submit the CommandGraph, say during a
    // SwapBuffers or ReadPixels call, we begin a primary Vulkan CommandBuffer, and walk the
    // CommandGraph, starting at the most senior nodes, recording SecondaryCommandBuffers inside
    // and outside RenderPasses as necessary, filled with the right load/store operations. Once
    // the primary CommandBuffer has recorded all of the SecondaryCommandBuffers from all the open
    // CommandGraphNodes, we submit the primary CommandBuffer to the VkQueue on the device.
    //
    // The Command Graph consists of an array of open Command Graph Nodes. It supports allocating new
    // nodes for the graph, which are linked via dependency relation calls in CommandGraphNode, and
    // also submitting the whole command graph via submitCommands.
    class CommandGraph final : angle::NonCopyable
    {
      public:
        explicit CommandGraph(bool enableGraphDiagnostics, angle::PoolAllocator *poolAllocator);
        ~CommandGraph();
    
        // Allocates a new CommandGraphNode and adds it to the list of current open nodes. No ordering
        // relations exist in the node by default. Call CommandGraphNode::SetHappensBeforeDependency
        // to set up dependency relations. If the node is a barrier, it will automatically add
        // dependencies between the previous barrier, the new barrier and all nodes in between.
        CommandGraphNode *allocateNode(CommandGraphNodeFunction function);
    
        angle::Result submitCommands(ContextVk *context,
                                     Serial serial,
                                     RenderPassCache *renderPassCache,
                                     PrimaryCommandBuffer *primaryCommandBuffer);
        bool empty() const;
        void clear();
    
        // The following create special-function nodes that don't require a graph resource.
        // Queries:
        void beginQuery(const QueryPool *queryPool, uint32_t queryIndex);
        void endQuery(const QueryPool *queryPool, uint32_t queryIndex);
        void writeTimestamp(const QueryPool *queryPool, uint32_t queryIndex);
        void beginTransformFeedbackEmulatedQuery();
        void endTransformFeedbackEmulatedQuery();
        // GLsync and EGLSync:
        void setFenceSync(const vk::Event &event);
        void waitFenceSync(const vk::Event &event);
        // Memory barriers:
        void memoryBarrier(VkFlags srcAccess, VkFlags dstAccess, VkPipelineStageFlags stages);
        // Debug markers:
        void insertDebugMarker(GLenum source, std::string &&marker);
        void pushDebugMarker(GLenum source, std::string &&marker);
        void popDebugMarker();
        // Host-visible buffer write availability operation:
        void makeHostVisibleBufferWriteAvailable();
    
        void onResourceUse(const SharedResourceUse &resourceUse);
        void releaseResourceUses();
    
      private:
        CommandGraphNode *allocateBarrierNode(CommandGraphNodeFunction function,
                                              CommandGraphResourceType resourceType,
                                              uintptr_t resourceID);
        void setNewBarrier(CommandGraphNode *newBarrier);
        CommandGraphNode *getLastBarrierNode(size_t *indexOut);
        void addDependenciesToNextBarrier(size_t begin, size_t end, CommandGraphNode *nextBarrier);
    
        void dumpGraphDotFile(std::ostream &out) const;
        void updateOverlay(ContextVk *contextVk) const;
        void releaseResourceUsesAndUpdateSerials(Serial serial);
    
        std::vector<CommandGraphNode *> mNodes;
        bool mEnableGraphDiagnostics;
        angle::PoolAllocator *mPoolAllocator;
    
        // A set of nodes (eventually) exist that act as barriers to guarantee submission order.  For
        // example, a glMemoryBarrier() calls would lead to such a barrier or beginning and ending a
        // query. This is because the graph can reorder operations if it sees fit.  Let's call a barrier
        // node Bi, and the other nodes Ni. The edges between Ni don't interest us.  Before a barrier is
        // inserted, we have:
        //
        // N0 N1 ... Na
        // \___\__/_/     (dependency egdes, which we don't care about so I'll stop drawing them.
        //      \/
        //
        // When the first barrier is inserted, we will have:
        //
        //     ______
        //    /  ____\
        //   /  /     \
        //  /  /      /\
        // N0 N1 ... Na B0
        //
        // This makes sure all N0..Na are called before B0.  From then on, B0 will be the current
        // "barrier point" which extends an edge to every next node:
        //
        //     ______
        //    /  ____\
        //   /  /     \
        //  /  /      /\
        // N0 N1 ... Na B0 Na+1 ... Nb
        //                \/       /
        //                 \______/
        //
        //
        // When the next barrier B1 is met, all nodes between B0 and B1 will add a depenency on B1 as
        // well, and the "barrier point" is updated.
        //
        //     ______
        //    /  ____\         ______         ______
        //   /  /     \       /      \       /      \
        //  /  /      /\     /       /\     /       /\
        // N0 N1 ... Na B0 Na+1 ... Nb B1 Nb+1 ... Nc B2 ...
        //                \/       /  /  \/       /  /
        //                 \______/  /    \______/  /
        //                  \_______/      \_______/
        //
        //
        // When barrier Bi is introduced, all nodes added since Bi-1 need to add a dependency to Bi
        // (including Bi-1). We therefore keep track of the node index of the last barrier that was
        // issued.
        static constexpr size_t kInvalidNodeIndex = std::numeric_limits<std::size_t>::max();
        size_t mLastBarrierIndex;
    
        std::vector<SharedResourceUse> mResourceUses;
    };
    
    // CommandGraphResource inlines.
    ANGLE_INLINE bool CommandGraphResource::hasStartedRenderPass() const
    {
        return hasChildlessWritingNode() && mCurrentWritingNode->getInsideRenderPassCommands()->valid();
    }
    
    ANGLE_INLINE void CommandGraphResource::updateCurrentAccessNodes()
    {
        // Clear dependencies if this is a new access.
        if (!mUse.isCurrentlyInGraph())
        {
            mCurrentWritingNode = nullptr;
            mCurrentReadingNodes.clear();
        }
    }
    
    ANGLE_INLINE void CommandGraphResource::onGraphAccess(CommandGraph *commandGraph)
    {
        updateCurrentAccessNodes();
    
        // Store reference to usage in graph.
        commandGraph->onResourceUse(mUse);
    }
    
    ANGLE_INLINE bool CommandGraphResource::appendToStartedRenderPass(CommandGraph *graph,
                                                                      const gl::Rectangle &renderArea,
                                                                      CommandBuffer **commandBufferOut)
    {
        updateCurrentAccessNodes();
    
        if (hasStartedRenderPass())
        {
            // Store reference to usage in graph.
            graph->onResourceUse(mUse);
    
            if (mCurrentWritingNode->getRenderPassRenderArea().encloses(renderArea))
            {
                *commandBufferOut = mCurrentWritingNode->getInsideRenderPassCommands();
                return true;
            }
        }
    
        return false;
    }
    
    ANGLE_INLINE bool CommandGraphResource::renderPassStartedButEmpty() const
    {
        return hasStartedRenderPass() && (!vk::CommandBuffer::CanKnowIfEmpty() ||
                                          mCurrentWritingNode->getInsideRenderPassCommands()->empty());
    }
    
    ANGLE_INLINE void CommandGraphResource::clearRenderPassColorAttachment(
        size_t attachmentIndex,
        const VkClearColorValue &clearValue)
    {
        ASSERT(renderPassStartedButEmpty());
        mCurrentWritingNode->clearRenderPassColorAttachment(attachmentIndex, clearValue);
    }
    
    ANGLE_INLINE void CommandGraphResource::clearRenderPassDepthAttachment(size_t attachmentIndex,
                                                                           float depth)
    {
        ASSERT(renderPassStartedButEmpty());
        mCurrentWritingNode->clearRenderPassDepthAttachment(attachmentIndex, depth);
    }
    
    ANGLE_INLINE void CommandGraphResource::clearRenderPassStencilAttachment(size_t attachmentIndex,
                                                                             uint32_t stencil)
    {
        ASSERT(renderPassStartedButEmpty());
        mCurrentWritingNode->clearRenderPassStencilAttachment(attachmentIndex, stencil);
    }
    
    ANGLE_INLINE void CommandGraphResource::invalidateRenderPassColorAttachment(size_t attachmentIndex)
    {
        ASSERT(hasStartedRenderPass());
        mCurrentWritingNode->invalidateRenderPassColorAttachment(attachmentIndex);
    }
    
    ANGLE_INLINE void CommandGraphResource::invalidateRenderPassDepthAttachment(size_t attachmentIndex)
    {
        ASSERT(hasStartedRenderPass());
        mCurrentWritingNode->invalidateRenderPassDepthAttachment(attachmentIndex);
    }
    
    ANGLE_INLINE void CommandGraphResource::invalidateRenderPassStencilAttachment(
        size_t attachmentIndex)
    {
        ASSERT(hasStartedRenderPass());
        mCurrentWritingNode->invalidateRenderPassStencilAttachment(attachmentIndex);
    }
    
    ANGLE_INLINE const gl::Rectangle &CommandGraphResource::getRenderPassRenderArea() const
    {
        ASSERT(hasStartedRenderPass());
        return mCurrentWritingNode->getRenderPassRenderArea();
    }
    
    ANGLE_INLINE void CommandGraphResource::addGlobalMemoryBarrier(VkFlags srcAccess,
                                                                   VkFlags dstAccess,
                                                                   VkPipelineStageFlags stages)
    {
        ASSERT(mCurrentWritingNode);
        mCurrentWritingNode->addGlobalMemoryBarrier(srcAccess, dstAccess, stages);
    }
    
    ANGLE_INLINE bool CommandGraphResource::hasChildlessWritingNode() const
    {
        // Note: currently, we don't have a resource that can issue both generic and special
        // commands.  We don't create read/write dependencies between mixed generic/special
        // resources either.  As such, we expect the function to always be generic here.  If such a
        // resource is added in the future, this can add a check for function == generic and fail if
        // false.
        ASSERT(mCurrentWritingNode == nullptr ||
               mCurrentWritingNode->getFunction() == CommandGraphNodeFunction::Generic);
        return (mCurrentWritingNode != nullptr && !mCurrentWritingNode->hasChildren());
    }
    
    // CommandGraph inlines.
    ANGLE_INLINE void CommandGraph::onResourceUse(const SharedResourceUse &resourceUse)
    {
        // Disabled the assert because of difficulties with ImageView references.
        // TODO(jmadill): Clean up with graph redesign. http://anglebug.com/4029
        // ASSERT(!empty());
        SharedResourceUse newUse;
        newUse.set(resourceUse);
        mResourceUses.emplace_back(std::move(newUse));
    }
    }  // namespace vk
    }  // namespace rx
    
    #endif  // LIBANGLE_RENDERER_VULKAN_COMMAND_GRAPH_H_