// // 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 #include #include #include #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 *sharedFenceOut); inline void resetSharedFence(vk::Shared *sharedFenceIn) { std::lock_guard lock(mMutex); sharedFenceIn->resetAndRecycle(&mRecyler); } private: std::mutex mMutex; vk::Recycler mRecyler; }; enum class CustomTask { Invalid = 0, // Process SecondaryCommandBuffer commands into the primary CommandBuffer. ProcessOutsideRenderPassCommands, ProcessRenderPassCommands, // 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, // Finish all pending work WaitIdle, // 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 initOutsideRenderPassProcessCommands(bool hasProtectedContent, OutsideRenderPassCommandBufferHelper *commandBuffer); void initRenderPassProcessCommands(bool hasProtectedContent, RenderPassCommandBufferHelper *commandBuffer, const RenderPass *renderPass); void initPresent(egl::ContextPriority priority, const VkPresentInfoKHR &presentInfo); void initFinishToSerial(Serial serial); void initWaitIdle(); void initFlushAndQueueSubmit(const std::vector &waitSemaphores, const std::vector &waitSemaphoreStageMasks, const Semaphore *semaphore, bool hasProtectedContent, egl::ContextPriority priority, SecondaryCommandPools *commandPools, GarbageList &¤tGarbage, SecondaryCommandBufferList &&commandBuffersToReset, Serial submitQueueSerial); void initOneOffQueueSubmit(VkCommandBuffer commandBufferHandle, bool hasProtectedContent, egl::ContextPriority priority, const Semaphore *waitSemaphore, VkPipelineStageFlags waitSemaphoreStageMask, 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 &getWaitSemaphores() { return mWaitSemaphores; } std::vector &getWaitSemaphoreStageMasks() { return mWaitSemaphoreStageMasks; } const Semaphore *getSemaphore() { return mSemaphore; } GarbageList &getGarbage() { return mGarbage; } SecondaryCommandBufferList &&getCommandBuffersToReset() { return std::move(mCommandBuffersToReset); } egl::ContextPriority getPriority() const { return mPriority; } bool hasProtectedContent() const { return mHasProtectedContent; } VkCommandBuffer getOneOffCommandBufferVk() const { return mOneOffCommandBufferVk; } const Semaphore *getOneOffWaitSemaphore() { return mOneOffWaitSemaphore; } VkPipelineStageFlags getOneOffWaitSemaphoreStageMask() { return mOneOffWaitSemaphoreStageMask; } const Fence *getOneOffFence() { return mOneOffFence; } const VkPresentInfoKHR &getPresentInfo() const { return mPresentInfo; } const RenderPass *getRenderPass() const { return mRenderPass; } OutsideRenderPassCommandBufferHelper *getOutsideRenderPassCommandBuffer() const { return mOutsideRenderPassCommandBuffer; } RenderPassCommandBufferHelper *getRenderPassCommandBuffer() const { return mRenderPassCommandBuffer; } SecondaryCommandPools *getCommandPools() const { return mCommandPools; } private: void copyPresentInfo(const VkPresentInfoKHR &other); CustomTask mTask; // ProcessCommands OutsideRenderPassCommandBufferHelper *mOutsideRenderPassCommandBuffer; RenderPassCommandBufferHelper *mRenderPassCommandBuffer; const RenderPass *mRenderPass; // Flush data std::vector mWaitSemaphores; std::vector mWaitSemaphoreStageMasks; const Semaphore *mSemaphore; SecondaryCommandPools *mCommandPools; GarbageList mGarbage; SecondaryCommandBufferList mCommandBuffersToReset; // 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 mRects; // Used by OneOffQueueSubmit VkCommandBuffer mOneOffCommandBufferVk; const Semaphore *mOneOffWaitSemaphore; VkPipelineStageFlags mOneOffWaitSemaphoreStageMask; 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); void resetSecondaryCommandBuffers(VkDevice device); PrimaryCommandBuffer primaryCommands; // commandPools is for secondary CommandBuffer allocation SecondaryCommandPools *commandPools; SecondaryCommandBufferList commandBuffersToReset; Shared fence; Serial serial; bool hasProtectedContent; }; class DeviceQueueMap; class QueueFamily final : angle::NonCopyable { public: static const uint32_t kInvalidIndex = std::numeric_limits::max(); static uint32_t FindIndex(const std::vector &queueFamilyProperties, VkQueueFlags flags, int32_t matchNumber, // 0 = first match, 1 = second match ... uint32_t *matchCount); static const uint32_t kQueueCount = static_cast(egl::ContextPriority::EnumCount); static const float kQueuePriorities[static_cast(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 { 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 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 angle::Result waitIdle(Context *context, uint64_t timeout) = 0; virtual Serial reserveSubmitSerial() = 0; virtual angle::Result submitFrame( Context *context, bool hasProtectedContent, egl::ContextPriority priority, const std::vector &waitSemaphores, const std::vector &waitSemaphoreStageMasks, const Semaphore *signalSemaphore, GarbageList &¤tGarbage, SecondaryCommandBufferList &&commandBuffersToReset, SecondaryCommandPools *commandPools, Serial submitQueueSerial) = 0; virtual angle::Result queueSubmitOneOff(Context *context, bool hasProtectedContent, egl::ContextPriority contextPriority, VkCommandBuffer commandBufferHandle, const Semaphore *waitSemaphore, VkPipelineStageFlags waitSemaphoreStageMask, 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, OutsideRenderPassCommandBufferHelper **outsideRPCommands) = 0; virtual angle::Result flushRenderPassCommands( Context *context, bool hasProtectedContent, const RenderPass &renderPass, RenderPassCommandBufferHelper **renderPassCommands) = 0; // For correct synchronization with external, in particular when asked to signal an external // semaphore, we need to ensure that there are no pending submissions. virtual angle::Result ensureNoPendingWork(Context *context) = 0; virtual Serial getLastCompletedQueueSerial() const = 0; virtual bool isBusy() 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; angle::Result waitIdle(Context *context, uint64_t timeout) override; Serial reserveSubmitSerial() override; angle::Result submitFrame(Context *context, bool hasProtectedContent, egl::ContextPriority priority, const std::vector &waitSemaphores, const std::vector &waitSemaphoreStageMasks, const Semaphore *signalSemaphore, GarbageList &¤tGarbage, SecondaryCommandBufferList &&commandBuffersToReset, SecondaryCommandPools *commandPools, Serial submitQueueSerial) override; angle::Result queueSubmitOneOff(Context *context, bool hasProtectedContent, egl::ContextPriority contextPriority, VkCommandBuffer commandBufferHandle, const Semaphore *waitSemaphore, VkPipelineStageFlags waitSemaphoreStageMask, 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, OutsideRenderPassCommandBufferHelper **outsideRPCommands) override; angle::Result flushRenderPassCommands( Context *context, bool hasProtectedContent, const RenderPass &renderPass, RenderPassCommandBufferHelper **renderPassCommands) override; angle::Result ensureNoPendingWork(Context *context) override { return angle::Result::Continue; } Serial getLastCompletedQueueSerial() const override; bool isBusy() 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(); } VkQueue getQueue(egl::ContextPriority priority) { return mQueueMap[priority]; } private: void releaseToCommandBatch(bool hasProtectedContent, PrimaryCommandBuffer &&commandBuffer, SecondaryCommandPools *commandPools, CommandBatch *batch); angle::Result retireFinishedCommands(Context *context, size_t finishedCount); angle::Result ensurePrimaryCommandBufferValid(Context *context, bool hasProtectedContent); bool allInFlightCommandsAreAfterSerial(Serial serial); 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 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 final : public Context, public CommandQueueInterface { public: CommandProcessor(RendererVk *renderer); ~CommandProcessor() override; 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; angle::Result waitIdle(Context *context, uint64_t timeout) override; Serial reserveSubmitSerial() override; angle::Result submitFrame(Context *context, bool hasProtectedContent, egl::ContextPriority priority, const std::vector &waitSemaphores, const std::vector &waitSemaphoreStageMasks, const Semaphore *signalSemaphore, GarbageList &¤tGarbage, SecondaryCommandBufferList &&commandBuffersToReset, SecondaryCommandPools *commandPools, Serial submitQueueSerial) override; angle::Result queueSubmitOneOff(Context *context, bool hasProtectedContent, egl::ContextPriority contextPriority, VkCommandBuffer commandBufferHandle, const Semaphore *waitSemaphore, VkPipelineStageFlags waitSemaphoreStageMask, 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, OutsideRenderPassCommandBufferHelper **outsideRPCommands) override; angle::Result flushRenderPassCommands( Context *context, bool hasProtectedContent, const RenderPass &renderPass, RenderPassCommandBufferHelper **renderPassCommands) override; angle::Result ensureNoPendingWork(Context *context) override; Serial getLastCompletedQueueSerial() const override; bool isBusy() const override; egl::ContextPriority getDriverPriority(egl::ContextPriority priority) { return mCommandQueue.getDriverPriority(priority); } uint32_t getDeviceQueueIndex() const { return mCommandQueue.getDeviceQueueIndex(); } VkQueue getQueue(egl::ContextPriority priority) { return mCommandQueue.getQueue(priority); } private: bool hasPendingError() const { std::lock_guard 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); // Used by main thread to wait for worker thread to complete all outstanding work. angle::Result waitForWorkComplete(Context *context); std::queue 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 mErrors; // Track present info std::mutex mSwapchainStatusMutex; std::condition_variable mSwapchainStatusCondition; std::map mSwapchainStatus; // Command queue worker thread. std::thread mTaskThread; }; // An RAII wrapper for locking and unlocking the command-queue mutex. On destructor, this class // handles device loss, if it has happened. This is because handling device loss affects the // command queue state, so it's deferred until the current scope that's using the command queue // state is finished. class ANGLE_NO_DISCARD ScopedCommandQueueLock final { public: ScopedCommandQueueLock(RendererVk *renderer, std::mutex &lock) : mRenderer(renderer), mLock(lock) { lock.lock(); } ~ScopedCommandQueueLock(); private: RendererVk *mRenderer; std::mutex &mLock; }; } // namespace vk } // namespace rx #endif // LIBANGLE_RENDERER_VULKAN_COMMAND_PROCESSOR_H_