1 // 2 // Copyright 2024 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 // RefCountedEvent: 7 // Manages reference count of VkEvent and its associated functions. 8 // 9 10 #ifndef LIBANGLE_RENDERER_VULKAN_REFCOUNTED_EVENT_H_ 11 #define LIBANGLE_RENDERER_VULKAN_REFCOUNTED_EVENT_H_ 12 13 #include <atomic> 14 #include <limits> 15 #include <queue> 16 17 #include "common/PackedEnums.h" 18 #include "common/SimpleMutex.h" 19 #include "common/debug.h" 20 #include "libANGLE/renderer/serial_utils.h" 21 #include "libANGLE/renderer/vulkan/vk_resource.h" 22 #include "libANGLE/renderer/vulkan/vk_utils.h" 23 #include "libANGLE/renderer/vulkan/vk_wrapper.h" 24 25 namespace rx 26 { 27 namespace vk 28 { 29 enum class ImageLayout; 30 31 // There are two ways to implement a barrier: Using VkCmdPipelineBarrier or VkCmdWaitEvents. The 32 // BarrierType enum will be passed around to indicate which barrier caller want to use. 33 enum class BarrierType 34 { 35 Pipeline, 36 Event, 37 }; 38 39 constexpr VkPipelineStageFlags kPreFragmentStageFlags = 40 VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT | 41 VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT; 42 43 constexpr VkPipelineStageFlags kAllShadersPipelineStageFlags = 44 kPreFragmentStageFlags | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | 45 VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; 46 47 constexpr VkPipelineStageFlags kAllDepthStencilPipelineStageFlags = 48 VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; 49 50 // Enum for predefined VkPipelineStageFlags set that VkEvent will be using. Because VkEvent has 51 // strict rules that waitEvent and setEvent must have matching VkPipelineStageFlags, it is desirable 52 // to keep VkEvent per VkPipelineStageFlags combination. This enum table enumerates all possible 53 // pipeline stage combinations that VkEvent used with. The enum maps to VkPipelineStageFlags via 54 // Renderer::getPipelineStageMask call. 55 enum class EventStage : uint32_t 56 { 57 Transfer = 0, 58 VertexShader = 1, 59 FragmentShader = 2, 60 ComputeShader = 3, 61 AllShaders = 4, 62 PreFragmentShaders = 5, 63 FragmentShadingRate = 6, 64 ColorAttachmentOutput = 7, 65 ColorAttachmentOutputAndFragmentShader = 8, 66 ColorAttachmentOutputAndFragmentShaderAndTransfer = 9, 67 ColorAttachmentOutputAndAllShaders = 10, 68 AllFragmentTest = 11, 69 AllFragmentTestAndFragmentShader = 12, 70 AllFragmentTestAndAllShaders = 13, 71 TransferAndComputeShader = 14, 72 InvalidEnum = 15, 73 EnumCount = InvalidEnum, 74 }; 75 76 // Initialize EventStage to VkPipelineStageFlags mapping table. 77 void InitializeEventAndPipelineStagesMap( 78 angle::PackedEnumMap<EventStage, VkPipelineStageFlags> *mapping, 79 VkPipelineStageFlags supportedVulkanPipelineStageMask); 80 81 // VkCmdWaitEvents requires srcStageMask must be the bitwise OR of the stageMask parameter used in 82 // previous calls to vkCmdSetEvent (See VUID-vkCmdWaitEvents-srcStageMask-01158). This mean we must 83 // keep the record of what stageMask each event has been used in VkCmdSetEvent call so that we can 84 // retrieve that information when we need to wait for the event. Instead of keeping just stageMask 85 // here, we keep the ImageLayout for now which gives us more information for debugging. 86 struct EventAndStage 87 { validEventAndStage88 bool valid() const { return event.valid(); } 89 Event event; 90 EventStage eventStage; 91 }; 92 93 // The VkCmdSetEvent is called after VkCmdEndRenderPass and all images that used at the given 94 // pipeline stage (i.e, they have the same stageMask) will be tracked by the same event. This means 95 // there will be multiple objects pointing to the same event. Events are thus reference counted so 96 // that we do not destroy it while other objects still referencing to it. 97 class RefCountedEvent final 98 { 99 public: RefCountedEvent()100 RefCountedEvent() { mHandle = nullptr; } ~RefCountedEvent()101 ~RefCountedEvent() { ASSERT(mHandle == nullptr); } 102 103 // Move constructor moves reference of the underline object from other to this. RefCountedEvent(RefCountedEvent && other)104 RefCountedEvent(RefCountedEvent &&other) 105 { 106 mHandle = other.mHandle; 107 other.mHandle = nullptr; 108 } 109 110 // Copy constructor adds reference to the underline object. RefCountedEvent(const RefCountedEvent & other)111 RefCountedEvent(const RefCountedEvent &other) 112 { 113 ASSERT(other.valid()); 114 mHandle = other.mHandle; 115 mHandle->addRef(); 116 } 117 118 // Move assignment moves reference of the underline object from other to this. 119 RefCountedEvent &operator=(RefCountedEvent &&other) 120 { 121 ASSERT(!valid()); 122 ASSERT(other.valid()); 123 std::swap(mHandle, other.mHandle); 124 return *this; 125 } 126 127 // Copy assignment adds reference to the underline object. 128 RefCountedEvent &operator=(const RefCountedEvent &other) 129 { 130 ASSERT(!valid()); 131 ASSERT(other.valid()); 132 mHandle = other.mHandle; 133 mHandle->addRef(); 134 return *this; 135 } 136 137 // Returns true if both points to the same underline object. 138 bool operator==(const RefCountedEvent &other) const { return mHandle == other.mHandle; } 139 140 // Create VkEvent and associated it with given layout. Returns true if success and false if 141 // failed. 142 bool init(Context *context, EventStage eventStage); 143 144 // Release one reference count to the underline Event object and destroy or recycle the handle 145 // to renderer's recycler if this is the very last reference. 146 void release(Renderer *renderer); 147 148 // Release one reference count to the underline Event object and destroy or recycle the handle 149 // to the context share group's recycler if this is the very last reference. 150 void release(Context *context); 151 152 // Destroy the event and mHandle. Caller must ensure there is no outstanding reference to the 153 // mHandle. 154 void destroy(VkDevice device); 155 valid()156 bool valid() const { return mHandle != nullptr; } 157 158 // Only intended for assertion in recycler validAndNoReference()159 bool validAndNoReference() const { return mHandle != nullptr && !mHandle->isReferenced(); } 160 161 // Returns the underlying Event object getEvent()162 const Event &getEvent() const 163 { 164 ASSERT(valid()); 165 return mHandle->get().event; 166 } 167 getEventStage()168 EventStage getEventStage() const 169 { 170 ASSERT(mHandle != nullptr); 171 return mHandle->get().eventStage; 172 } 173 174 private: 175 // Release one reference count to the underline Event object and destroy or recycle the handle 176 // to the provided recycler if this is the very last reference. 177 friend class RefCountedEventsGarbage; 178 template <typename RecyclerT> 179 void releaseImpl(Renderer *renderer, RecyclerT *recycler); 180 181 RefCounted<EventAndStage> *mHandle; 182 }; 183 using RefCountedEventCollector = std::deque<RefCountedEvent>; 184 185 // Tracks a list of RefCountedEvents per EventStage. 186 struct EventMaps 187 { 188 angle::PackedEnumMap<EventStage, RefCountedEvent> map; 189 // The mask is used to accelerate the loop of map 190 angle::PackedEnumBitSet<EventStage, uint64_t> mask; 191 // Only used by RenderPassCommandBufferHelper 192 angle::PackedEnumMap<EventStage, VkEvent> vkEvents; 193 }; 194 195 // This class tracks a vector of RefcountedEvent garbage. For performance reason, instead of 196 // individually tracking each VkEvent garbage, we collect all events that are accessed in the 197 // CommandBufferHelper into this class. After we submit the command buffer, we treat this vector of 198 // events as one garbage object and add it to renderer's garbage list. The garbage clean up will 199 // decrement the refCount and destroy event only when last refCount goes away. Basically all GPU 200 // usage will use one refCount and that refCount ensures we never destroy event until GPU is 201 // finished. 202 class RefCountedEventsGarbage final 203 { 204 public: 205 RefCountedEventsGarbage() = default; ~RefCountedEventsGarbage()206 ~RefCountedEventsGarbage() { ASSERT(mRefCountedEvents.empty()); } 207 RefCountedEventsGarbage(const QueueSerial & queueSerial,RefCountedEventCollector && refCountedEvents)208 RefCountedEventsGarbage(const QueueSerial &queueSerial, 209 RefCountedEventCollector &&refCountedEvents) 210 : mQueueSerial(queueSerial), mRefCountedEvents(std::move(refCountedEvents)) 211 { 212 ASSERT(!mRefCountedEvents.empty()); 213 } 214 215 void destroy(Renderer *renderer); 216 217 // Check the queue serial and release the events to recycler if GPU finished. 218 bool releaseIfComplete(Renderer *renderer, RefCountedEventsGarbageRecycler *recycler); 219 220 // Check the queue serial and move all events to releasedBucket if GPU finished. This is only 221 // used by RefCountedEventRecycler. 222 bool moveIfComplete(Renderer *renderer, std::deque<RefCountedEventCollector> *releasedBucket); 223 224 // Move event to the garbage list add(RefCountedEvent && event)225 void add(RefCountedEvent &&event) { mRefCountedEvents.emplace_back(std::move(event)); } 226 227 // Move the vector of events to the garbage list add(RefCountedEventCollector && events)228 void add(RefCountedEventCollector &&events) 229 { 230 mRefCountedEvents.insert(mRefCountedEvents.end(), events.begin(), events.end()); 231 ASSERT(events.empty()); 232 } 233 234 // Make a copy of event (which adds another refcount to the VkEvent) and add the copied event to 235 // the garbages add(const RefCountedEvent & event)236 void add(const RefCountedEvent &event) 237 { 238 RefCountedEvent localEventCopy = event; 239 mRefCountedEvents.emplace_back(std::move(localEventCopy)); 240 ASSERT(!localEventCopy.valid()); 241 ASSERT(event.valid()); 242 } 243 empty()244 bool empty() const { return mRefCountedEvents.empty(); } 245 size()246 size_t size() const { return mRefCountedEvents.size(); } 247 248 private: 249 QueueSerial mQueueSerial; 250 RefCountedEventCollector mRefCountedEvents; 251 }; 252 253 // Two levels of RefCountedEvents recycle system: For the performance reason, we have two levels of 254 // events recycler system. The first level is per ShareGroupVk, which owns RefCountedEventRecycler. 255 // RefCountedEvent garbage is added to it without any lock. Once GPU complete, the refCount is 256 // decremented. When the last refCount goes away, it goes into mEventsToReset. Note that since 257 // ShareGoupVk access is already protected by context share lock at the API level, so no lock is 258 // taken and reference counting is not atomic. At RefCountedEventsGarbageRecycler::cleanup time, the 259 // entire mEventsToReset is added into renderer's list. The renderer owns RefCountedEventRecycler 260 // list, and all access to it is protected with simple mutex lock. When any context calls 261 // OutsideRenderPassCommandBufferHelper::flushToPrimary, mEventsToReset is retrieved from renderer 262 // and the reset commands is added to the command buffer. The events are then moved to the 263 // renderer's garbage list. They are checked and along with renderer's garbage cleanup and if 264 // completed, they get moved to renderer's mEventsToReuse list. When a RefCountedEvent is needed, we 265 // always dip into ShareGroupVk's mEventsToReuse list. If its empty, it then dip into renderer's 266 // mEventsToReuse and grab a collector of events and try to reuse. That way the traffic into 267 // renderer is minimized as most of calls will be contained in SHareGroupVk. 268 269 // Thread safe event recycler, protected by its own lock. 270 class RefCountedEventRecycler final 271 { 272 public: RefCountedEventRecycler()273 RefCountedEventRecycler() {} ~RefCountedEventRecycler()274 ~RefCountedEventRecycler() 275 { 276 ASSERT(mEventsToReset.empty()); 277 ASSERT(mResettingQueue.empty()); 278 ASSERT(mEventsToReuse.empty()); 279 } 280 281 void destroy(VkDevice device); 282 283 // Add single event to the toReset list recycle(RefCountedEvent && garbageObject)284 void recycle(RefCountedEvent &&garbageObject) 285 { 286 ASSERT(garbageObject.validAndNoReference()); 287 std::lock_guard<angle::SimpleMutex> lock(mMutex); 288 if (mEventsToReset.empty()) 289 { 290 mEventsToReset.emplace_back(); 291 } 292 mEventsToReset.back().emplace_back(std::move(garbageObject)); 293 } 294 295 // Add a list of events to the toReset list recycle(RefCountedEventCollector && garbageObjects)296 void recycle(RefCountedEventCollector &&garbageObjects) 297 { 298 ASSERT(!garbageObjects.empty()); 299 for (const RefCountedEvent &event : garbageObjects) 300 { 301 ASSERT(event.validAndNoReference()); 302 } 303 std::lock_guard<angle::SimpleMutex> lock(mMutex); 304 mEventsToReset.emplace_back(std::move(garbageObjects)); 305 } 306 307 // Reset all events in the toReset list and move them to the toReuse list 308 void resetEvents(Context *context, 309 const QueueSerial queueSerial, 310 PrimaryCommandBuffer *commandbuffer); 311 312 // Clean up the resetting event list and move completed events to the toReuse list. 313 void cleanupResettingEvents(Renderer *renderer); 314 315 // Fetch a list of events that are ready to be reused. Returns true if eventsToReuseOut is 316 // returned. 317 bool fetchEventsToReuse(RefCountedEventCollector *eventsToReuseOut); 318 319 private: 320 angle::SimpleMutex mMutex; 321 // RefCountedEvent list that has been released, needs to be reset. 322 std::deque<RefCountedEventCollector> mEventsToReset; 323 // RefCountedEvent list that is currently resetting. 324 std::queue<RefCountedEventsGarbage> mResettingQueue; 325 // RefCountedEvent list that already has been reset. Ready to be reused. 326 std::deque<RefCountedEventCollector> mEventsToReuse; 327 }; 328 329 // Not thread safe event garbage collection and recycler. Caller must ensure the thread safety. It 330 // is intended to use by ShareGroupVk which all access should already protected by share context 331 // lock. 332 class RefCountedEventsGarbageRecycler final 333 { 334 public: RefCountedEventsGarbageRecycler()335 RefCountedEventsGarbageRecycler() : mGarbageCount(0) {} 336 ~RefCountedEventsGarbageRecycler(); 337 338 // Release all garbage and free events. 339 void destroy(Renderer *renderer); 340 341 // Walk the garbage list and move completed garbage to free list 342 void cleanup(Renderer *renderer); 343 collectGarbage(const QueueSerial & queueSerial,RefCountedEventCollector && refCountedEvents)344 void collectGarbage(const QueueSerial &queueSerial, RefCountedEventCollector &&refCountedEvents) 345 { 346 mGarbageCount += refCountedEvents.size(); 347 mGarbageQueue.emplace(queueSerial, std::move(refCountedEvents)); 348 } 349 recycle(RefCountedEvent && garbageObject)350 void recycle(RefCountedEvent &&garbageObject) 351 { 352 ASSERT(garbageObject.validAndNoReference()); 353 mEventsToReset.emplace_back(std::move(garbageObject)); 354 } 355 356 bool fetch(Renderer *renderer, RefCountedEvent *outObject); 357 getGarbageCount()358 size_t getGarbageCount() const { return mGarbageCount; } 359 360 private: 361 RefCountedEventCollector mEventsToReset; 362 std::queue<RefCountedEventsGarbage> mGarbageQueue; 363 Recycler<RefCountedEvent> mEventsToReuse; 364 size_t mGarbageCount; 365 }; 366 367 // This wraps data and API for vkCmdWaitEvent call 368 class EventBarrier : angle::NonCopyable 369 { 370 public: EventBarrier()371 EventBarrier() 372 : mSrcStageMask(0), 373 mDstStageMask(0), 374 mMemoryBarrierSrcAccess(0), 375 mMemoryBarrierDstAccess(0), 376 mImageMemoryBarrierCount(0), 377 mEvent(VK_NULL_HANDLE) 378 {} 379 EventBarrier(VkPipelineStageFlags srcStageMask,VkPipelineStageFlags dstStageMask,VkAccessFlags srcAccess,VkAccessFlags dstAccess,const VkEvent & event)380 EventBarrier(VkPipelineStageFlags srcStageMask, 381 VkPipelineStageFlags dstStageMask, 382 VkAccessFlags srcAccess, 383 VkAccessFlags dstAccess, 384 const VkEvent &event) 385 : mSrcStageMask(srcStageMask), 386 mDstStageMask(dstStageMask), 387 mMemoryBarrierSrcAccess(srcAccess), 388 mMemoryBarrierDstAccess(dstAccess), 389 mImageMemoryBarrierCount(0), 390 mEvent(event) 391 { 392 ASSERT(mEvent != VK_NULL_HANDLE); 393 } 394 EventBarrier(VkPipelineStageFlags srcStageMask,VkPipelineStageFlags dstStageMask,const VkEvent & event,const VkImageMemoryBarrier & imageMemoryBarrier)395 EventBarrier(VkPipelineStageFlags srcStageMask, 396 VkPipelineStageFlags dstStageMask, 397 const VkEvent &event, 398 const VkImageMemoryBarrier &imageMemoryBarrier) 399 : mSrcStageMask(srcStageMask), 400 mDstStageMask(dstStageMask), 401 mMemoryBarrierSrcAccess(0), 402 mMemoryBarrierDstAccess(0), 403 mImageMemoryBarrierCount(1), 404 mEvent(event), 405 mImageMemoryBarrier(imageMemoryBarrier) 406 { 407 ASSERT(mEvent != VK_NULL_HANDLE); 408 ASSERT(mImageMemoryBarrier.image != VK_NULL_HANDLE); 409 ASSERT(mImageMemoryBarrier.pNext == nullptr); 410 } 411 EventBarrier(EventBarrier && other)412 EventBarrier(EventBarrier &&other) 413 { 414 mSrcStageMask = other.mSrcStageMask; 415 mDstStageMask = other.mDstStageMask; 416 mMemoryBarrierSrcAccess = other.mMemoryBarrierSrcAccess; 417 mMemoryBarrierDstAccess = other.mMemoryBarrierDstAccess; 418 mImageMemoryBarrierCount = other.mImageMemoryBarrierCount; 419 std::swap(mEvent, other.mEvent); 420 std::swap(mImageMemoryBarrier, other.mImageMemoryBarrier); 421 other.mSrcStageMask = 0; 422 other.mDstStageMask = 0; 423 other.mMemoryBarrierSrcAccess = 0; 424 other.mMemoryBarrierDstAccess = 0; 425 other.mImageMemoryBarrierCount = 0; 426 } 427 ~EventBarrier()428 ~EventBarrier() {} 429 isEmpty()430 bool isEmpty() const { return mEvent == VK_NULL_HANDLE; } 431 hasEvent(const VkEvent & event)432 bool hasEvent(const VkEvent &event) const { return mEvent == event; } 433 addAdditionalStageAccess(VkPipelineStageFlags dstStageMask,VkAccessFlags dstAccess)434 void addAdditionalStageAccess(VkPipelineStageFlags dstStageMask, VkAccessFlags dstAccess) 435 { 436 mDstStageMask |= dstStageMask; 437 mMemoryBarrierDstAccess |= dstAccess; 438 } 439 440 void execute(PrimaryCommandBuffer *primary); 441 442 void addDiagnosticsString(std::ostringstream &out) const; 443 444 private: 445 friend class EventBarrierArray; 446 VkPipelineStageFlags mSrcStageMask; 447 VkPipelineStageFlags mDstStageMask; 448 VkAccessFlags mMemoryBarrierSrcAccess; 449 VkAccessFlags mMemoryBarrierDstAccess; 450 uint32_t mImageMemoryBarrierCount; 451 VkEvent mEvent; 452 VkImageMemoryBarrier mImageMemoryBarrier; 453 }; 454 455 class EventBarrierArray final 456 { 457 public: isEmpty()458 bool isEmpty() const { return mBarriers.empty(); } 459 460 void execute(Renderer *renderer, PrimaryCommandBuffer *primary); 461 462 // Add the additional stageMask to the existing waitEvent. 463 void addAdditionalStageAccess(const RefCountedEvent &waitEvent, 464 VkPipelineStageFlags dstStageMask, 465 VkAccessFlags dstAccess); 466 467 void addMemoryEvent(Renderer *renderer, 468 const RefCountedEvent &waitEvent, 469 VkPipelineStageFlags dstStageMask, 470 VkAccessFlags dstAccess); 471 472 void addImageEvent(Renderer *renderer, 473 const RefCountedEvent &waitEvent, 474 VkPipelineStageFlags dstStageMask, 475 const VkImageMemoryBarrier &imageMemoryBarrier); 476 reset()477 void reset() { ASSERT(mBarriers.empty()); } 478 479 void addDiagnosticsString(std::ostringstream &out) const; 480 481 private: 482 std::deque<EventBarrier> mBarriers; 483 }; 484 } // namespace vk 485 } // namespace rx 486 #endif // LIBANGLE_RENDERER_VULKAN_REFCOUNTED_EVENT_H_ 487