Edit

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

Branch :

  • Show log

    Commit

  • Author : Shahbaz Youssefi
    Date : 2021-09-29 16:40:11
    Hash : 4cb34637
    Message : Vulkan: Fix crash in async queue The change also fixes a regression introduced by 2d79918fdb0276cdb57488d72abd803250f962a3 which caused a segfault when async queue is used. Unfortunately, due to anglebug.com/6437, async queue tests are not run on the bots at the moment. This will be fixed in a follow up change. Bug: angleproject:6436 Change-Id: I3c2f63549149fbd9c9ab61107e98e8015cdb1c77 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3193419 Reviewed-by: Jamie Madill <jmadill@chromium.org> Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>

  • src/libANGLE/renderer/vulkan/CommandProcessor.h
  • //
    // Copyright 2020 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.
    //
    // CommandProcessor.h:
    //    A class to process and submit Vulkan command buffers that can be
    //    used in an asynchronous worker thread.
    //
    
    #ifndef LIBANGLE_RENDERER_VULKAN_COMMAND_PROCESSOR_H_
    #define LIBANGLE_RENDERER_VULKAN_COMMAND_PROCESSOR_H_
    
    #include <condition_variable>
    #include <mutex>
    #include <queue>
    #include <thread>
    
    #include "common/vulkan/vk_headers.h"
    #include "libANGLE/renderer/vulkan/PersistentCommandPool.h"
    #include "libANGLE/renderer/vulkan/vk_helpers.h"
    
    namespace rx
    {
    class RendererVk;
    class CommandProcessor;
    
    namespace vk
    {
    enum class SubmitPolicy
    {
        AllowDeferred,
        EnsureSubmitted,
    };
    
    class FenceRecycler
    {
      public:
        FenceRecycler() {}
        ~FenceRecycler() {}
        void destroy(vk::Context *context);
    
        angle::Result newSharedFence(vk::Context *context, vk::Shared<vk::Fence> *sharedFenceOut);
        inline void resetSharedFence(vk::Shared<vk::Fence> *sharedFenceIn)
        {
            std::lock_guard<std::mutex> lock(mMutex);
            sharedFenceIn->resetAndRecycle(&mRecyler);
        }
    
      private:
        std::mutex mMutex;
        vk::Recycler<vk::Fence> mRecyler;
    };
    
    enum class CustomTask
    {
        Invalid = 0,
        // Process SecondaryCommandBuffer commands into the primary CommandBuffer.
        ProcessCommands,
        // End the current command buffer and submit commands to the queue
        FlushAndQueueSubmit,
        // Submit custom command buffer, excludes some state management
        OneOffQueueSubmit,
        // Finish queue commands up to given serial value, process garbage
        FinishToSerial,
        // Execute QueuePresent
        Present,
        // do cleanup processing on completed commands
        // TODO: https://issuetracker.google.com/170312581 - should be able to remove
        // checkCompletedCommands command with fence refactor.
        CheckCompletedCommands,
        // Exit the command processor thread
        Exit,
    };
    
    // CommandProcessorTask interface
    class CommandProcessorTask
    {
      public:
        CommandProcessorTask() { initTask(); }
    
        void initTask();
    
        void initTask(CustomTask command) { mTask = command; }
    
        void initProcessCommands(bool hasProtectedContent,
                                 CommandBufferHelper *commandBuffer,
                                 const RenderPass *renderPass);
    
        void initPresent(egl::ContextPriority priority, const VkPresentInfoKHR &presentInfo);
    
        void initFinishToSerial(Serial serial);
    
        void initFlushAndQueueSubmit(const std::vector<VkSemaphore> &waitSemaphores,
                                     const std::vector<VkPipelineStageFlags> &waitSemaphoreStageMasks,
                                     const Semaphore *semaphore,
                                     bool hasProtectedContent,
                                     egl::ContextPriority priority,
                                     CommandPool *commandPool,
                                     GarbageList &&currentGarbage,
                                     Serial submitQueueSerial);
    
        void initOneOffQueueSubmit(VkCommandBuffer commandBufferHandle,
                                   bool hasProtectedContent,
                                   egl::ContextPriority priority,
                                   const Fence *fence,
                                   Serial submitQueueSerial);
    
        CommandProcessorTask &operator=(CommandProcessorTask &&rhs);
    
        CommandProcessorTask(CommandProcessorTask &&other) : CommandProcessorTask()
        {
            *this = std::move(other);
        }
    
        void setQueueSerial(Serial serial) { mSerial = serial; }
        Serial getQueueSerial() const { return mSerial; }
        CustomTask getTaskCommand() { return mTask; }
        std::vector<VkSemaphore> &getWaitSemaphores() { return mWaitSemaphores; }
        std::vector<VkPipelineStageFlags> &getWaitSemaphoreStageMasks()
        {
            return mWaitSemaphoreStageMasks;
        }
        const Semaphore *getSemaphore() { return mSemaphore; }
        GarbageList &getGarbage() { return mGarbage; }
        egl::ContextPriority getPriority() const { return mPriority; }
        bool hasProtectedContent() const { return mHasProtectedContent; }
        VkCommandBuffer getOneOffCommandBufferVk() const { return mOneOffCommandBufferVk; }
        const Fence *getOneOffFence() { return mOneOffFence; }
        const VkPresentInfoKHR &getPresentInfo() const { return mPresentInfo; }
        const RenderPass *getRenderPass() const { return mRenderPass; }
        CommandBufferHelper *getCommandBuffer() const { return mCommandBuffer; }
        CommandPool *getCommandPool() const { return mCommandPool; }
    
      private:
        void copyPresentInfo(const VkPresentInfoKHR &other);
    
        CustomTask mTask;
    
        // ProcessCommands
        const RenderPass *mRenderPass;
        CommandBufferHelper *mCommandBuffer;
    
        // Flush data
        std::vector<VkSemaphore> mWaitSemaphores;
        std::vector<VkPipelineStageFlags> mWaitSemaphoreStageMasks;
        const Semaphore *mSemaphore;
        CommandPool *mCommandPool;
        GarbageList mGarbage;
    
        // FinishToSerial & Flush command data
        Serial mSerial;
    
        // Present command data
        VkPresentInfoKHR mPresentInfo;
        VkSwapchainKHR mSwapchain;
        VkSemaphore mWaitSemaphore;
        uint32_t mImageIndex;
        // Used by Present if supportsIncrementalPresent is enabled
        VkPresentRegionKHR mPresentRegion;
        VkPresentRegionsKHR mPresentRegions;
        std::vector<VkRectLayerKHR> mRects;
    
        // Used by OneOffQueueSubmit
        VkCommandBuffer mOneOffCommandBufferVk;
        const Fence *mOneOffFence;
    
        // Flush, Present & QueueWaitIdle data
        egl::ContextPriority mPriority;
        bool mHasProtectedContent;
    };
    
    struct CommandBatch final : angle::NonCopyable
    {
        CommandBatch();
        ~CommandBatch();
        CommandBatch(CommandBatch &&other);
        CommandBatch &operator=(CommandBatch &&other);
    
        void destroy(VkDevice device);
    
        PrimaryCommandBuffer primaryCommands;
        // commandPool is for secondary CommandBuffer allocation
        CommandPool *commandPool;
        Shared<Fence> fence;
        Serial serial;
        bool hasProtectedContent;
    };
    
    class DeviceQueueMap;
    
    class QueueFamily final : angle::NonCopyable
    {
      public:
        static const uint32_t kInvalidIndex = std::numeric_limits<uint32_t>::max();
    
        static uint32_t FindIndex(const std::vector<VkQueueFamilyProperties> &queueFamilyProperties,
                                  VkQueueFlags flags,
                                  int32_t matchNumber,  // 0 = first match, 1 = second match ...
                                  uint32_t *matchCount);
        static const uint32_t kQueueCount = static_cast<uint32_t>(egl::ContextPriority::EnumCount);
        static const float kQueuePriorities[static_cast<uint32_t>(egl::ContextPriority::EnumCount)];
    
        QueueFamily() : mProperties{}, mIndex(kInvalidIndex) {}
        ~QueueFamily() {}
    
        void initialize(const VkQueueFamilyProperties &queueFamilyProperties, uint32_t index);
        bool valid() const { return (mIndex != kInvalidIndex); }
        uint32_t getIndex() const { return mIndex; }
        const VkQueueFamilyProperties *getProperties() const { return &mProperties; }
        bool isGraphics() const { return ((mProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) > 0); }
        bool isCompute() const { return ((mProperties.queueFlags & VK_QUEUE_COMPUTE_BIT) > 0); }
        bool supportsProtected() const
        {
            return ((mProperties.queueFlags & VK_QUEUE_PROTECTED_BIT) > 0);
        }
        uint32_t getDeviceQueueCount() const { return mProperties.queueCount; }
    
        DeviceQueueMap initializeQueueMap(VkDevice device,
                                          bool makeProtected,
                                          uint32_t queueIndex,
                                          uint32_t queueCount);
    
      private:
        VkQueueFamilyProperties mProperties;
        uint32_t mIndex;
    
        void getDeviceQueue(VkDevice device, bool makeProtected, uint32_t queueIndex, VkQueue *queue);
    };
    
    class DeviceQueueMap : public angle::PackedEnumMap<egl::ContextPriority, VkQueue>
    {
        friend QueueFamily;
    
      public:
        DeviceQueueMap() : mIndex(vk::QueueFamily::kInvalidIndex), mIsProtected(false) {}
        DeviceQueueMap(uint32_t queueFamilyIndex, bool isProtected)
            : mIndex(queueFamilyIndex), mIsProtected(isProtected)
        {}
        ~DeviceQueueMap();
        DeviceQueueMap &operator=(const DeviceQueueMap &other);
    
        bool valid() const { return (mIndex != QueueFamily::kInvalidIndex); }
        uint32_t getIndex() const { return mIndex; }
        bool isProtected() const { return mIsProtected; }
        egl::ContextPriority getDevicePriority(egl::ContextPriority priority) const;
    
      private:
        uint32_t mIndex;
        bool mIsProtected;
        angle::PackedEnumMap<egl::ContextPriority, egl::ContextPriority> mPriorities;
    };
    
    class CommandQueueInterface : angle::NonCopyable
    {
      public:
        virtual ~CommandQueueInterface() {}
    
        virtual angle::Result init(Context *context, const DeviceQueueMap &queueMap) = 0;
        virtual void destroy(Context *context)                                       = 0;
    
        virtual void handleDeviceLost(RendererVk *renderer) = 0;
    
        // Wait until the desired serial has been completed.
        virtual angle::Result finishToSerial(Context *context,
                                             Serial finishSerial,
                                             uint64_t timeout) = 0;
        virtual Serial reserveSubmitSerial()                   = 0;
        virtual angle::Result submitFrame(
            Context *context,
            bool hasProtectedContent,
            egl::ContextPriority priority,
            const std::vector<VkSemaphore> &waitSemaphores,
            const std::vector<VkPipelineStageFlags> &waitSemaphoreStageMasks,
            const Semaphore *signalSemaphore,
            GarbageList &&currentGarbage,
            CommandPool *commandPool,
            Serial submitQueueSerial)                                      = 0;
        virtual angle::Result queueSubmitOneOff(Context *context,
                                                bool hasProtectedContent,
                                                egl::ContextPriority contextPriority,
                                                VkCommandBuffer commandBufferHandle,
                                                const Fence *fence,
                                                SubmitPolicy submitPolicy,
                                                Serial submitQueueSerial)  = 0;
        virtual VkResult queuePresent(egl::ContextPriority contextPriority,
                                      const VkPresentInfoKHR &presentInfo) = 0;
    
        virtual angle::Result waitForSerialWithUserTimeout(vk::Context *context,
                                                           Serial serial,
                                                           uint64_t timeout,
                                                           VkResult *result) = 0;
    
        // Check to see which batches have finished completion (forward progress for
        // the last completed serial, for example for when the application busy waits on a query
        // result). It would be nice if we didn't have to expose this for QueryVk::getResult.
        virtual angle::Result checkCompletedCommands(Context *context) = 0;
    
        virtual angle::Result flushOutsideRPCommands(Context *context,
                                                     bool hasProtectedContent,
                                                     CommandBufferHelper **outsideRPCommands)   = 0;
        virtual angle::Result flushRenderPassCommands(Context *context,
                                                      bool hasProtectedContent,
                                                      const RenderPass &renderPass,
                                                      CommandBufferHelper **renderPassCommands) = 0;
    
        virtual Serial getLastSubmittedQueueSerial() const = 0;
        virtual Serial getLastCompletedQueueSerial() const = 0;
        virtual Serial getCurrentQueueSerial() const       = 0;
    };
    
    class CommandQueue final : public CommandQueueInterface
    {
      public:
        CommandQueue();
        ~CommandQueue() override;
    
        angle::Result init(Context *context, const DeviceQueueMap &queueMap) override;
        void destroy(Context *context) override;
        void clearAllGarbage(RendererVk *renderer);
    
        void handleDeviceLost(RendererVk *renderer) override;
    
        angle::Result finishToSerial(Context *context, Serial finishSerial, uint64_t timeout) override;
    
        Serial reserveSubmitSerial() override;
    
        angle::Result submitFrame(Context *context,
                                  bool hasProtectedContent,
                                  egl::ContextPriority priority,
                                  const std::vector<VkSemaphore> &waitSemaphores,
                                  const std::vector<VkPipelineStageFlags> &waitSemaphoreStageMasks,
                                  const Semaphore *signalSemaphore,
                                  GarbageList &&currentGarbage,
                                  CommandPool *commandPool,
                                  Serial submitQueueSerial) override;
    
        angle::Result queueSubmitOneOff(Context *context,
                                        bool hasProtectedContent,
                                        egl::ContextPriority contextPriority,
                                        VkCommandBuffer commandBufferHandle,
                                        const Fence *fence,
                                        SubmitPolicy submitPolicy,
                                        Serial submitQueueSerial) override;
    
        VkResult queuePresent(egl::ContextPriority contextPriority,
                              const VkPresentInfoKHR &presentInfo) override;
    
        angle::Result waitForSerialWithUserTimeout(vk::Context *context,
                                                   Serial serial,
                                                   uint64_t timeout,
                                                   VkResult *result) override;
    
        angle::Result checkCompletedCommands(Context *context) override;
    
        angle::Result flushOutsideRPCommands(Context *context,
                                             bool hasProtectedContent,
                                             CommandBufferHelper **outsideRPCommands) override;
        angle::Result flushRenderPassCommands(Context *context,
                                              bool hasProtectedContent,
                                              const RenderPass &renderPass,
                                              CommandBufferHelper **renderPassCommands) override;
    
        Serial getLastSubmittedQueueSerial() const override;
        Serial getLastCompletedQueueSerial() const override;
        Serial getCurrentQueueSerial() const override;
    
        angle::Result queueSubmit(Context *context,
                                  egl::ContextPriority contextPriority,
                                  const VkSubmitInfo &submitInfo,
                                  const Fence *fence,
                                  Serial submitQueueSerial);
    
        egl::ContextPriority getDriverPriority(egl::ContextPriority priority)
        {
            return mQueueMap.getDevicePriority(priority);
        }
        uint32_t getDeviceQueueIndex() const { return mQueueMap.getIndex(); }
    
      private:
        void releaseToCommandBatch(bool hasProtectedContent,
                                   PrimaryCommandBuffer &&commandBuffer,
                                   CommandPool *commandPool,
                                   CommandBatch *batch);
        angle::Result retireFinishedCommands(Context *context, size_t finishedCount);
        angle::Result ensurePrimaryCommandBufferValid(Context *context, bool hasProtectedContent);
    
        bool allInFlightCommandsAreAfterSerial(Serial serial);
    
        VkQueue getQueue(egl::ContextPriority priority) { return mQueueMap[priority]; }
    
        PrimaryCommandBuffer &getCommandBuffer(bool hasProtectedContent)
        {
            if (hasProtectedContent)
            {
                return mProtectedPrimaryCommands;
            }
            else
            {
                return mPrimaryCommands;
            }
        }
    
        PersistentCommandPool &getCommandPool(bool hasProtectedContent)
        {
            if (hasProtectedContent)
            {
                return mProtectedPrimaryCommandPool;
            }
            else
            {
                return mPrimaryCommandPool;
            }
        }
    
        GarbageQueue mGarbageQueue;
    
        std::vector<CommandBatch> mInFlightCommands;
    
        // Keeps a free list of reusable primary command buffers.
        PrimaryCommandBuffer mPrimaryCommands;
        PersistentCommandPool mPrimaryCommandPool;
        PrimaryCommandBuffer mProtectedPrimaryCommands;
        PersistentCommandPool mProtectedPrimaryCommandPool;
    
        // Queue serial management.
        AtomicSerialFactory mQueueSerialFactory;
        Serial mLastCompletedQueueSerial;
        Serial mLastSubmittedQueueSerial;
        Serial mCurrentQueueSerial;
    
        // QueueMap
        DeviceQueueMap mQueueMap;
    
        FenceRecycler mFenceRecycler;
    };
    
    // CommandProcessor is used to dispatch work to the GPU when the asyncCommandQueue feature is
    // enabled. Issuing the |destroy| command will cause the worker thread to clean up it's resources
    // and shut down. This command is sent when the renderer instance shuts down. Tasks are defined by
    // the CommandQueue interface.
    
    class CommandProcessor : public Context, public CommandQueueInterface
    {
      public:
        CommandProcessor(RendererVk *renderer);
        ~CommandProcessor() override;
    
        // Used by main thread to wait for worker thread to complete all outstanding work.
        // TODO(jmadill): Make private. b/172704839
        angle::Result waitForWorkComplete(Context *context);
        angle::Result finishAllWork(Context *context);
    
        VkResult getLastPresentResult(VkSwapchainKHR swapchain)
        {
            return getLastAndClearPresentResult(swapchain);
        }
    
        // vk::Context
        void handleError(VkResult result,
                         const char *file,
                         const char *function,
                         unsigned int line) override;
    
        // CommandQueueInterface
        angle::Result init(Context *context, const DeviceQueueMap &queueMap) override;
    
        void destroy(Context *context) override;
    
        void handleDeviceLost(RendererVk *renderer) override;
    
        angle::Result finishToSerial(Context *context, Serial finishSerial, uint64_t timeout) override;
    
        Serial reserveSubmitSerial() override;
    
        angle::Result submitFrame(Context *context,
                                  bool hasProtectedContent,
                                  egl::ContextPriority priority,
                                  const std::vector<VkSemaphore> &waitSemaphores,
                                  const std::vector<VkPipelineStageFlags> &waitSemaphoreStageMasks,
                                  const Semaphore *signalSemaphore,
                                  GarbageList &&currentGarbage,
                                  CommandPool *commandPool,
                                  Serial submitQueueSerial) override;
    
        angle::Result queueSubmitOneOff(Context *context,
                                        bool hasProtectedContent,
                                        egl::ContextPriority contextPriority,
                                        VkCommandBuffer commandBufferHandle,
                                        const Fence *fence,
                                        SubmitPolicy submitPolicy,
                                        Serial submitQueueSerial) override;
        VkResult queuePresent(egl::ContextPriority contextPriority,
                              const VkPresentInfoKHR &presentInfo) override;
    
        angle::Result waitForSerialWithUserTimeout(vk::Context *context,
                                                   Serial serial,
                                                   uint64_t timeout,
                                                   VkResult *result) override;
    
        angle::Result checkCompletedCommands(Context *context) override;
    
        angle::Result flushOutsideRPCommands(Context *context,
                                             bool hasProtectedContent,
                                             CommandBufferHelper **outsideRPCommands) override;
        angle::Result flushRenderPassCommands(Context *context,
                                              bool hasProtectedContent,
                                              const RenderPass &renderPass,
                                              CommandBufferHelper **renderPassCommands) override;
    
        Serial getLastSubmittedQueueSerial() const override;
        Serial getLastCompletedQueueSerial() const override;
        Serial getCurrentQueueSerial() const override;
    
        egl::ContextPriority getDriverPriority(egl::ContextPriority priority)
        {
            return mCommandQueue.getDriverPriority(priority);
        }
        uint32_t getDeviceQueueIndex() const { return mCommandQueue.getDeviceQueueIndex(); }
    
      private:
        bool hasPendingError() const
        {
            std::lock_guard<std::mutex> queueLock(mErrorMutex);
            return !mErrors.empty();
        }
        angle::Result checkAndPopPendingError(Context *errorHandlingContext);
    
        // Entry point for command processor thread, calls processTasksImpl to do the
        // work. called by RendererVk::initializeDevice on main thread
        void processTasks();
    
        // Called asynchronously from main thread to queue work that is then processed by the worker
        // thread
        void queueCommand(CommandProcessorTask &&task);
    
        // Command processor thread, called by processTasks. The loop waits for work to
        // be submitted from a separate thread.
        angle::Result processTasksImpl(bool *exitThread);
    
        // Command processor thread, process a task
        angle::Result processTask(CommandProcessorTask *task);
    
        VkResult getLastAndClearPresentResult(VkSwapchainKHR swapchain);
        VkResult present(egl::ContextPriority priority, const VkPresentInfoKHR &presentInfo);
    
        std::queue<CommandProcessorTask> mTasks;
        mutable std::mutex mWorkerMutex;
        // Signal worker thread when work is available
        std::condition_variable mWorkAvailableCondition;
        // Signal main thread when all work completed
        mutable std::condition_variable mWorkerIdleCondition;
        // Track worker thread Idle state for assertion purposes
        bool mWorkerThreadIdle;
        CommandQueue mCommandQueue;
    
        mutable std::mutex mQueueSerialMutex;
    
        mutable std::mutex mErrorMutex;
        std::queue<Error> mErrors;
    
        // Track present info
        std::mutex mSwapchainStatusMutex;
        std::condition_variable mSwapchainStatusCondition;
        std::map<VkSwapchainKHR, VkResult> mSwapchainStatus;
    
        // Command queue worker thread.
        std::thread mTaskThread;
    };
    
    }  // namespace vk
    
    }  // namespace rx
    
    #endif  // LIBANGLE_RENDERER_VULKAN_COMMAND_PROCESSOR_H_