• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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