1 // 2 // Copyright 2020 The ANGLE Project Authors. All rights reserved. 3 // Use of this source code is governed by a BSD-style license that can be 4 // found in the LICENSE file. 5 // 6 // CommandProcessor.h: 7 // A class to process and submit Vulkan command buffers that can be 8 // used in an asynchronous worker thread. 9 // 10 11 #ifndef LIBANGLE_RENDERER_VULKAN_COMMAND_PROCESSOR_H_ 12 #define LIBANGLE_RENDERER_VULKAN_COMMAND_PROCESSOR_H_ 13 14 #include <condition_variable> 15 #include <mutex> 16 #include <queue> 17 #include <thread> 18 19 #include "common/vulkan/vk_headers.h" 20 #include "libANGLE/renderer/vulkan/PersistentCommandPool.h" 21 #include "libANGLE/renderer/vulkan/vk_helpers.h" 22 23 namespace rx 24 { 25 class RendererVk; 26 class CommandProcessor; 27 28 namespace vk 29 { 30 enum class SubmitPolicy 31 { 32 AllowDeferred, 33 EnsureSubmitted, 34 }; 35 36 class FenceRecycler 37 { 38 public: FenceRecycler()39 FenceRecycler() {} ~FenceRecycler()40 ~FenceRecycler() {} 41 void destroy(vk::Context *context); 42 43 angle::Result newSharedFence(vk::Context *context, vk::Shared<vk::Fence> *sharedFenceOut); resetSharedFence(vk::Shared<vk::Fence> * sharedFenceIn)44 inline void resetSharedFence(vk::Shared<vk::Fence> *sharedFenceIn) 45 { 46 std::lock_guard<std::mutex> lock(mMutex); 47 sharedFenceIn->resetAndRecycle(&mRecyler); 48 } 49 50 private: 51 std::mutex mMutex; 52 vk::Recycler<vk::Fence> mRecyler; 53 }; 54 55 enum class CustomTask 56 { 57 Invalid = 0, 58 // Process SecondaryCommandBuffer commands into the primary CommandBuffer. 59 ProcessCommands, 60 // End the current command buffer and submit commands to the queue 61 FlushAndQueueSubmit, 62 // Submit custom command buffer, excludes some state management 63 OneOffQueueSubmit, 64 // Finish queue commands up to given serial value, process garbage 65 FinishToSerial, 66 // Finish all pending work 67 WaitIdle, 68 // Execute QueuePresent 69 Present, 70 // do cleanup processing on completed commands 71 // TODO: https://issuetracker.google.com/170312581 - should be able to remove 72 // checkCompletedCommands command with fence refactor. 73 CheckCompletedCommands, 74 // Exit the command processor thread 75 Exit, 76 }; 77 78 // CommandProcessorTask interface 79 class CommandProcessorTask 80 { 81 public: CommandProcessorTask()82 CommandProcessorTask() { initTask(); } 83 84 void initTask(); 85 initTask(CustomTask command)86 void initTask(CustomTask command) { mTask = command; } 87 88 void initProcessCommands(bool hasProtectedContent, 89 CommandBufferHelper *commandBuffer, 90 const RenderPass *renderPass); 91 92 void initPresent(egl::ContextPriority priority, const VkPresentInfoKHR &presentInfo); 93 94 void initFinishToSerial(Serial serial); 95 96 void initWaitIdle(); 97 98 void initFlushAndQueueSubmit(const std::vector<VkSemaphore> &waitSemaphores, 99 const std::vector<VkPipelineStageFlags> &waitSemaphoreStageMasks, 100 const Semaphore *semaphore, 101 bool hasProtectedContent, 102 egl::ContextPriority priority, 103 CommandPool *commandPool, 104 GarbageList &¤tGarbage, 105 std::vector<CommandBuffer> &&commandBuffersToReset, 106 Serial submitQueueSerial); 107 108 void initOneOffQueueSubmit(VkCommandBuffer commandBufferHandle, 109 bool hasProtectedContent, 110 egl::ContextPriority priority, 111 const Semaphore *waitSemaphore, 112 VkPipelineStageFlags waitSemaphoreStageMask, 113 const Fence *fence, 114 Serial submitQueueSerial); 115 116 CommandProcessorTask &operator=(CommandProcessorTask &&rhs); 117 CommandProcessorTask(CommandProcessorTask && other)118 CommandProcessorTask(CommandProcessorTask &&other) : CommandProcessorTask() 119 { 120 *this = std::move(other); 121 } 122 setQueueSerial(Serial serial)123 void setQueueSerial(Serial serial) { mSerial = serial; } getQueueSerial()124 Serial getQueueSerial() const { return mSerial; } getTaskCommand()125 CustomTask getTaskCommand() { return mTask; } getWaitSemaphores()126 std::vector<VkSemaphore> &getWaitSemaphores() { return mWaitSemaphores; } getWaitSemaphoreStageMasks()127 std::vector<VkPipelineStageFlags> &getWaitSemaphoreStageMasks() 128 { 129 return mWaitSemaphoreStageMasks; 130 } getSemaphore()131 const Semaphore *getSemaphore() { return mSemaphore; } getGarbage()132 GarbageList &getGarbage() { return mGarbage; } getCommandBuffersToReset()133 std::vector<CommandBuffer> &getCommandBuffersToReset() { return mCommandBuffersToReset; } getPriority()134 egl::ContextPriority getPriority() const { return mPriority; } hasProtectedContent()135 bool hasProtectedContent() const { return mHasProtectedContent; } getOneOffCommandBufferVk()136 VkCommandBuffer getOneOffCommandBufferVk() const { return mOneOffCommandBufferVk; } getOneOffWaitSemaphore()137 const Semaphore *getOneOffWaitSemaphore() { return mOneOffWaitSemaphore; } getOneOffWaitSemaphoreStageMask()138 VkPipelineStageFlags getOneOffWaitSemaphoreStageMask() { return mOneOffWaitSemaphoreStageMask; } getOneOffFence()139 const Fence *getOneOffFence() { return mOneOffFence; } getPresentInfo()140 const VkPresentInfoKHR &getPresentInfo() const { return mPresentInfo; } getRenderPass()141 const RenderPass *getRenderPass() const { return mRenderPass; } getCommandBuffer()142 CommandBufferHelper *getCommandBuffer() const { return mCommandBuffer; } getCommandPool()143 CommandPool *getCommandPool() const { return mCommandPool; } 144 145 private: 146 void copyPresentInfo(const VkPresentInfoKHR &other); 147 148 CustomTask mTask; 149 150 // ProcessCommands 151 const RenderPass *mRenderPass; 152 CommandBufferHelper *mCommandBuffer; 153 154 // Flush data 155 std::vector<VkSemaphore> mWaitSemaphores; 156 std::vector<VkPipelineStageFlags> mWaitSemaphoreStageMasks; 157 const Semaphore *mSemaphore; 158 CommandPool *mCommandPool; 159 GarbageList mGarbage; 160 std::vector<CommandBuffer> mCommandBuffersToReset; 161 162 // FinishToSerial & Flush command data 163 Serial mSerial; 164 165 // Present command data 166 VkPresentInfoKHR mPresentInfo; 167 VkSwapchainKHR mSwapchain; 168 VkSemaphore mWaitSemaphore; 169 uint32_t mImageIndex; 170 // Used by Present if supportsIncrementalPresent is enabled 171 VkPresentRegionKHR mPresentRegion; 172 VkPresentRegionsKHR mPresentRegions; 173 std::vector<VkRectLayerKHR> mRects; 174 175 // Used by OneOffQueueSubmit 176 VkCommandBuffer mOneOffCommandBufferVk; 177 const Semaphore *mOneOffWaitSemaphore; 178 VkPipelineStageFlags mOneOffWaitSemaphoreStageMask; 179 const Fence *mOneOffFence; 180 181 // Flush, Present & QueueWaitIdle data 182 egl::ContextPriority mPriority; 183 bool mHasProtectedContent; 184 }; 185 186 struct CommandBatch final : angle::NonCopyable 187 { 188 CommandBatch(); 189 ~CommandBatch(); 190 CommandBatch(CommandBatch &&other); 191 CommandBatch &operator=(CommandBatch &&other); 192 193 void destroy(VkDevice device); 194 void resetSecondaryCommandBuffers(VkDevice device); 195 196 PrimaryCommandBuffer primaryCommands; 197 // commandPool is for secondary CommandBuffer allocation 198 CommandPool *commandPool; 199 std::vector<CommandBuffer> commandBuffersToReset; 200 Shared<Fence> fence; 201 Serial serial; 202 bool hasProtectedContent; 203 }; 204 205 class DeviceQueueMap; 206 207 class QueueFamily final : angle::NonCopyable 208 { 209 public: 210 static const uint32_t kInvalidIndex = std::numeric_limits<uint32_t>::max(); 211 212 static uint32_t FindIndex(const std::vector<VkQueueFamilyProperties> &queueFamilyProperties, 213 VkQueueFlags flags, 214 int32_t matchNumber, // 0 = first match, 1 = second match ... 215 uint32_t *matchCount); 216 static const uint32_t kQueueCount = static_cast<uint32_t>(egl::ContextPriority::EnumCount); 217 static const float kQueuePriorities[static_cast<uint32_t>(egl::ContextPriority::EnumCount)]; 218 QueueFamily()219 QueueFamily() : mProperties{}, mIndex(kInvalidIndex) {} ~QueueFamily()220 ~QueueFamily() {} 221 222 void initialize(const VkQueueFamilyProperties &queueFamilyProperties, uint32_t index); valid()223 bool valid() const { return (mIndex != kInvalidIndex); } getIndex()224 uint32_t getIndex() const { return mIndex; } getProperties()225 const VkQueueFamilyProperties *getProperties() const { return &mProperties; } isGraphics()226 bool isGraphics() const { return ((mProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) > 0); } isCompute()227 bool isCompute() const { return ((mProperties.queueFlags & VK_QUEUE_COMPUTE_BIT) > 0); } supportsProtected()228 bool supportsProtected() const 229 { 230 return ((mProperties.queueFlags & VK_QUEUE_PROTECTED_BIT) > 0); 231 } getDeviceQueueCount()232 uint32_t getDeviceQueueCount() const { return mProperties.queueCount; } 233 234 DeviceQueueMap initializeQueueMap(VkDevice device, 235 bool makeProtected, 236 uint32_t queueIndex, 237 uint32_t queueCount); 238 239 private: 240 VkQueueFamilyProperties mProperties; 241 uint32_t mIndex; 242 243 void getDeviceQueue(VkDevice device, bool makeProtected, uint32_t queueIndex, VkQueue *queue); 244 }; 245 246 class DeviceQueueMap : public angle::PackedEnumMap<egl::ContextPriority, VkQueue> 247 { 248 friend QueueFamily; 249 250 public: DeviceQueueMap()251 DeviceQueueMap() : mIndex(vk::QueueFamily::kInvalidIndex), mIsProtected(false) {} DeviceQueueMap(uint32_t queueFamilyIndex,bool isProtected)252 DeviceQueueMap(uint32_t queueFamilyIndex, bool isProtected) 253 : mIndex(queueFamilyIndex), mIsProtected(isProtected) 254 {} 255 ~DeviceQueueMap(); 256 DeviceQueueMap &operator=(const DeviceQueueMap &other); 257 valid()258 bool valid() const { return (mIndex != QueueFamily::kInvalidIndex); } getIndex()259 uint32_t getIndex() const { return mIndex; } isProtected()260 bool isProtected() const { return mIsProtected; } 261 egl::ContextPriority getDevicePriority(egl::ContextPriority priority) const; 262 263 private: 264 uint32_t mIndex; 265 bool mIsProtected; 266 angle::PackedEnumMap<egl::ContextPriority, egl::ContextPriority> mPriorities; 267 }; 268 269 class CommandQueueInterface : angle::NonCopyable 270 { 271 public: ~CommandQueueInterface()272 virtual ~CommandQueueInterface() {} 273 274 virtual angle::Result init(Context *context, const DeviceQueueMap &queueMap) = 0; 275 virtual void destroy(Context *context) = 0; 276 277 virtual void handleDeviceLost(RendererVk *renderer) = 0; 278 279 // Wait until the desired serial has been completed. 280 virtual angle::Result finishToSerial(Context *context, 281 Serial finishSerial, 282 uint64_t timeout) = 0; 283 virtual angle::Result waitIdle(Context *context, uint64_t timeout) = 0; 284 virtual Serial reserveSubmitSerial() = 0; 285 virtual angle::Result submitFrame( 286 Context *context, 287 bool hasProtectedContent, 288 egl::ContextPriority priority, 289 const std::vector<VkSemaphore> &waitSemaphores, 290 const std::vector<VkPipelineStageFlags> &waitSemaphoreStageMasks, 291 const Semaphore *signalSemaphore, 292 GarbageList &¤tGarbage, 293 std::vector<CommandBuffer> &&commandBuffersToReset, 294 CommandPool *commandPool, 295 Serial submitQueueSerial) = 0; 296 virtual angle::Result queueSubmitOneOff(Context *context, 297 bool hasProtectedContent, 298 egl::ContextPriority contextPriority, 299 VkCommandBuffer commandBufferHandle, 300 const Semaphore *waitSemaphore, 301 VkPipelineStageFlags waitSemaphoreStageMask, 302 const Fence *fence, 303 SubmitPolicy submitPolicy, 304 Serial submitQueueSerial) = 0; 305 virtual VkResult queuePresent(egl::ContextPriority contextPriority, 306 const VkPresentInfoKHR &presentInfo) = 0; 307 308 virtual angle::Result waitForSerialWithUserTimeout(vk::Context *context, 309 Serial serial, 310 uint64_t timeout, 311 VkResult *result) = 0; 312 313 // Check to see which batches have finished completion (forward progress for 314 // the last completed serial, for example for when the application busy waits on a query 315 // result). It would be nice if we didn't have to expose this for QueryVk::getResult. 316 virtual angle::Result checkCompletedCommands(Context *context) = 0; 317 318 virtual angle::Result flushOutsideRPCommands(Context *context, 319 bool hasProtectedContent, 320 CommandBufferHelper **outsideRPCommands) = 0; 321 virtual angle::Result flushRenderPassCommands(Context *context, 322 bool hasProtectedContent, 323 const RenderPass &renderPass, 324 CommandBufferHelper **renderPassCommands) = 0; 325 326 // For correct synchronization with external, in particular when asked to signal an external 327 // semaphore, we need to ensure that there are no pending submissions. 328 virtual angle::Result ensureNoPendingWork(Context *context) = 0; 329 330 virtual Serial getLastCompletedQueueSerial() const = 0; 331 virtual bool isBusy() const = 0; 332 }; 333 334 class CommandQueue final : public CommandQueueInterface 335 { 336 public: 337 CommandQueue(); 338 ~CommandQueue() override; 339 340 angle::Result init(Context *context, const DeviceQueueMap &queueMap) override; 341 void destroy(Context *context) override; 342 void clearAllGarbage(RendererVk *renderer); 343 344 void handleDeviceLost(RendererVk *renderer) override; 345 346 angle::Result finishToSerial(Context *context, Serial finishSerial, uint64_t timeout) override; 347 angle::Result waitIdle(Context *context, uint64_t timeout) override; 348 349 Serial reserveSubmitSerial() override; 350 351 angle::Result submitFrame(Context *context, 352 bool hasProtectedContent, 353 egl::ContextPriority priority, 354 const std::vector<VkSemaphore> &waitSemaphores, 355 const std::vector<VkPipelineStageFlags> &waitSemaphoreStageMasks, 356 const Semaphore *signalSemaphore, 357 GarbageList &¤tGarbage, 358 std::vector<CommandBuffer> &&commandBuffersToReset, 359 CommandPool *commandPool, 360 Serial submitQueueSerial) override; 361 362 angle::Result queueSubmitOneOff(Context *context, 363 bool hasProtectedContent, 364 egl::ContextPriority contextPriority, 365 VkCommandBuffer commandBufferHandle, 366 const Semaphore *waitSemaphore, 367 VkPipelineStageFlags waitSemaphoreStageMask, 368 const Fence *fence, 369 SubmitPolicy submitPolicy, 370 Serial submitQueueSerial) override; 371 372 VkResult queuePresent(egl::ContextPriority contextPriority, 373 const VkPresentInfoKHR &presentInfo) override; 374 375 angle::Result waitForSerialWithUserTimeout(vk::Context *context, 376 Serial serial, 377 uint64_t timeout, 378 VkResult *result) override; 379 380 angle::Result checkCompletedCommands(Context *context) override; 381 382 angle::Result flushOutsideRPCommands(Context *context, 383 bool hasProtectedContent, 384 CommandBufferHelper **outsideRPCommands) override; 385 angle::Result flushRenderPassCommands(Context *context, 386 bool hasProtectedContent, 387 const RenderPass &renderPass, 388 CommandBufferHelper **renderPassCommands) override; 389 ensureNoPendingWork(Context * context)390 angle::Result ensureNoPendingWork(Context *context) override { return angle::Result::Continue; } 391 392 Serial getLastCompletedQueueSerial() const override; 393 bool isBusy() const override; 394 395 angle::Result queueSubmit(Context *context, 396 egl::ContextPriority contextPriority, 397 const VkSubmitInfo &submitInfo, 398 const Fence *fence, 399 Serial submitQueueSerial); 400 getDriverPriority(egl::ContextPriority priority)401 egl::ContextPriority getDriverPriority(egl::ContextPriority priority) 402 { 403 return mQueueMap.getDevicePriority(priority); 404 } getDeviceQueueIndex()405 uint32_t getDeviceQueueIndex() const { return mQueueMap.getIndex(); } 406 getQueue(egl::ContextPriority priority)407 VkQueue getQueue(egl::ContextPriority priority) { return mQueueMap[priority]; } 408 409 private: 410 void releaseToCommandBatch(bool hasProtectedContent, 411 PrimaryCommandBuffer &&commandBuffer, 412 CommandPool *commandPool, 413 CommandBatch *batch); 414 angle::Result retireFinishedCommands(Context *context, size_t finishedCount); 415 angle::Result ensurePrimaryCommandBufferValid(Context *context, bool hasProtectedContent); 416 417 bool allInFlightCommandsAreAfterSerial(Serial serial); 418 getCommandBuffer(bool hasProtectedContent)419 PrimaryCommandBuffer &getCommandBuffer(bool hasProtectedContent) 420 { 421 if (hasProtectedContent) 422 { 423 return mProtectedPrimaryCommands; 424 } 425 else 426 { 427 return mPrimaryCommands; 428 } 429 } 430 getCommandPool(bool hasProtectedContent)431 PersistentCommandPool &getCommandPool(bool hasProtectedContent) 432 { 433 if (hasProtectedContent) 434 { 435 return mProtectedPrimaryCommandPool; 436 } 437 else 438 { 439 return mPrimaryCommandPool; 440 } 441 } 442 443 GarbageQueue mGarbageQueue; 444 445 std::vector<CommandBatch> mInFlightCommands; 446 447 // Keeps a free list of reusable primary command buffers. 448 PrimaryCommandBuffer mPrimaryCommands; 449 PersistentCommandPool mPrimaryCommandPool; 450 PrimaryCommandBuffer mProtectedPrimaryCommands; 451 PersistentCommandPool mProtectedPrimaryCommandPool; 452 453 // Queue serial management. 454 AtomicSerialFactory mQueueSerialFactory; 455 Serial mLastCompletedQueueSerial; 456 Serial mLastSubmittedQueueSerial; 457 Serial mCurrentQueueSerial; 458 459 // QueueMap 460 DeviceQueueMap mQueueMap; 461 462 FenceRecycler mFenceRecycler; 463 }; 464 465 // CommandProcessor is used to dispatch work to the GPU when the asyncCommandQueue feature is 466 // enabled. Issuing the |destroy| command will cause the worker thread to clean up it's resources 467 // and shut down. This command is sent when the renderer instance shuts down. Tasks are defined by 468 // the CommandQueue interface. 469 470 class CommandProcessor : public Context, public CommandQueueInterface 471 { 472 public: 473 CommandProcessor(RendererVk *renderer); 474 ~CommandProcessor() override; 475 getLastPresentResult(VkSwapchainKHR swapchain)476 VkResult getLastPresentResult(VkSwapchainKHR swapchain) 477 { 478 return getLastAndClearPresentResult(swapchain); 479 } 480 481 // vk::Context 482 void handleError(VkResult result, 483 const char *file, 484 const char *function, 485 unsigned int line) override; 486 487 // CommandQueueInterface 488 angle::Result init(Context *context, const DeviceQueueMap &queueMap) override; 489 490 void destroy(Context *context) override; 491 492 void handleDeviceLost(RendererVk *renderer) override; 493 494 angle::Result finishToSerial(Context *context, Serial finishSerial, uint64_t timeout) override; 495 496 angle::Result waitIdle(Context *context, uint64_t timeout) override; 497 498 Serial reserveSubmitSerial() override; 499 500 angle::Result submitFrame(Context *context, 501 bool hasProtectedContent, 502 egl::ContextPriority priority, 503 const std::vector<VkSemaphore> &waitSemaphores, 504 const std::vector<VkPipelineStageFlags> &waitSemaphoreStageMasks, 505 const Semaphore *signalSemaphore, 506 GarbageList &¤tGarbage, 507 std::vector<CommandBuffer> &&commandBuffersToReset, 508 CommandPool *commandPool, 509 Serial submitQueueSerial) override; 510 511 angle::Result queueSubmitOneOff(Context *context, 512 bool hasProtectedContent, 513 egl::ContextPriority contextPriority, 514 VkCommandBuffer commandBufferHandle, 515 const Semaphore *waitSemaphore, 516 VkPipelineStageFlags waitSemaphoreStageMask, 517 const Fence *fence, 518 SubmitPolicy submitPolicy, 519 Serial submitQueueSerial) override; 520 VkResult queuePresent(egl::ContextPriority contextPriority, 521 const VkPresentInfoKHR &presentInfo) override; 522 523 angle::Result waitForSerialWithUserTimeout(vk::Context *context, 524 Serial serial, 525 uint64_t timeout, 526 VkResult *result) override; 527 528 angle::Result checkCompletedCommands(Context *context) override; 529 530 angle::Result flushOutsideRPCommands(Context *context, 531 bool hasProtectedContent, 532 CommandBufferHelper **outsideRPCommands) override; 533 angle::Result flushRenderPassCommands(Context *context, 534 bool hasProtectedContent, 535 const RenderPass &renderPass, 536 CommandBufferHelper **renderPassCommands) override; 537 538 angle::Result ensureNoPendingWork(Context *context) override; 539 540 Serial getLastCompletedQueueSerial() const override; 541 bool isBusy() const override; 542 getDriverPriority(egl::ContextPriority priority)543 egl::ContextPriority getDriverPriority(egl::ContextPriority priority) 544 { 545 return mCommandQueue.getDriverPriority(priority); 546 } getDeviceQueueIndex()547 uint32_t getDeviceQueueIndex() const { return mCommandQueue.getDeviceQueueIndex(); } getQueue(egl::ContextPriority priority)548 VkQueue getQueue(egl::ContextPriority priority) { return mCommandQueue.getQueue(priority); } 549 550 private: hasPendingError()551 bool hasPendingError() const 552 { 553 std::lock_guard<std::mutex> queueLock(mErrorMutex); 554 return !mErrors.empty(); 555 } 556 angle::Result checkAndPopPendingError(Context *errorHandlingContext); 557 558 // Entry point for command processor thread, calls processTasksImpl to do the 559 // work. called by RendererVk::initializeDevice on main thread 560 void processTasks(); 561 562 // Called asynchronously from main thread to queue work that is then processed by the worker 563 // thread 564 void queueCommand(CommandProcessorTask &&task); 565 566 // Command processor thread, called by processTasks. The loop waits for work to 567 // be submitted from a separate thread. 568 angle::Result processTasksImpl(bool *exitThread); 569 570 // Command processor thread, process a task 571 angle::Result processTask(CommandProcessorTask *task); 572 573 VkResult getLastAndClearPresentResult(VkSwapchainKHR swapchain); 574 VkResult present(egl::ContextPriority priority, const VkPresentInfoKHR &presentInfo); 575 576 // Used by main thread to wait for worker thread to complete all outstanding work. 577 angle::Result waitForWorkComplete(Context *context); 578 579 std::queue<CommandProcessorTask> mTasks; 580 mutable std::mutex mWorkerMutex; 581 // Signal worker thread when work is available 582 std::condition_variable mWorkAvailableCondition; 583 // Signal main thread when all work completed 584 mutable std::condition_variable mWorkerIdleCondition; 585 // Track worker thread Idle state for assertion purposes 586 bool mWorkerThreadIdle; 587 CommandQueue mCommandQueue; 588 589 mutable std::mutex mQueueSerialMutex; 590 591 mutable std::mutex mErrorMutex; 592 std::queue<Error> mErrors; 593 594 // Track present info 595 std::mutex mSwapchainStatusMutex; 596 std::condition_variable mSwapchainStatusCondition; 597 std::map<VkSwapchainKHR, VkResult> mSwapchainStatus; 598 599 // Command queue worker thread. 600 std::thread mTaskThread; 601 }; 602 603 } // namespace vk 604 605 } // namespace rx 606 607 #endif // LIBANGLE_RENDERER_VULKAN_COMMAND_PROCESSOR_H_ 608