blob: 086cc4b9132595f52083188b4e32806bcedd13e0 [file]
//
// Copyright 2024 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.
//
// RefCountedEvent:
// Manages reference count of VkEvent and its associated functions.
//
#ifndef LIBANGLE_RENDERER_VULKAN_REFCOUNTED_EVENT_H_
#define LIBANGLE_RENDERER_VULKAN_REFCOUNTED_EVENT_H_
#include <atomic>
#include <limits>
#include <queue>
#include "common/PackedEnums.h"
#include "common/SimpleMutex.h"
#include "common/debug.h"
#include "libANGLE/renderer/serial_utils.h"
#include "libANGLE/renderer/vulkan/vk_resource.h"
#include "libANGLE/renderer/vulkan/vk_utils.h"
#include "libANGLE/renderer/vulkan/vk_wrapper.h"
namespace rx
{
namespace vk
{
class Context;
enum class ImageAccess;
// There are two ways to implement a barrier: Using VkCmdPipelineBarrier or VkCmdWaitEvents. The
// BarrierType enum will be passed around to indicate which barrier caller want to use.
enum class BarrierType
{
Pipeline,
Event,
};
constexpr VkPipelineStageFlags kPreFragmentStageFlags =
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT |
VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT;
constexpr VkPipelineStageFlags kAllShadersPipelineStageFlags =
kPreFragmentStageFlags | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
constexpr VkPipelineStageFlags kAllDepthStencilPipelineStageFlags =
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
constexpr VkPipelineStageFlags kFragmentAndAttachmentPipelineStageFlags =
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
// We group VK_PIPELINE_STAGE_*_BITs into different groups. The expectation is that execution within
// Fragment/PreFragment/Compute will not overlap. This information is used to optimize the usage of
// VkEvent where we try to not use it when we know that it will not provide benefits over
// pipelineBarriers.
enum class PipelineStageGroup : uint8_t
{
Other,
PreFragmentOnly,
FragmentOnly,
ComputeOnly,
InvalidEnum,
EnumCount = InvalidEnum,
};
class PipelineStageAccessHeuristic final
{
public:
constexpr PipelineStageAccessHeuristic() = default;
constexpr PipelineStageAccessHeuristic(PipelineStageGroup pipelineStageGroup)
{
for (size_t i = 0; i < kHeuristicWindowSize; i++)
{
mHeuristicBits <<= kPipelineStageGroupBitShift;
mHeuristicBits |= ToUnderlying(pipelineStageGroup);
}
}
void onAccess(PipelineStageGroup pipelineStageGroup)
{
mHeuristicBits <<= kPipelineStageGroupBitShift;
mHeuristicBits |= ToUnderlying(pipelineStageGroup);
}
constexpr bool operator==(const PipelineStageAccessHeuristic &other) const
{
return mHeuristicBits == other.mHeuristicBits;
}
private:
static constexpr size_t kPipelineStageGroupBitShift = 2;
static_assert(ToUnderlying(PipelineStageGroup::EnumCount) <=
(1 << kPipelineStageGroupBitShift));
static constexpr size_t kHeuristicWindowSize = 8;
angle::BitSet16<kHeuristicWindowSize * kPipelineStageGroupBitShift> mHeuristicBits;
};
static constexpr PipelineStageAccessHeuristic kPipelineStageAccessFragmentOnly =
PipelineStageAccessHeuristic(PipelineStageGroup::FragmentOnly);
static constexpr PipelineStageAccessHeuristic kPipelineStageAccessComputeOnly =
PipelineStageAccessHeuristic(PipelineStageGroup::ComputeOnly);
static constexpr PipelineStageAccessHeuristic kPipelineStageAccessPreFragmentOnly =
PipelineStageAccessHeuristic(PipelineStageGroup::PreFragmentOnly);
// Enum for predefined VkPipelineStageFlags set that VkEvent will be using. Because VkEvent has
// strict rules that waitEvent and setEvent must have matching VkPipelineStageFlags, it is desirable
// to keep VkEvent per VkPipelineStageFlags combination. This enum table enumerates all possible
// pipeline stage combinations that VkEvent used with. The enum maps to VkPipelineStageFlags via
// Renderer::getPipelineStageMask call.
enum class EventStage : uint32_t
{
Transfer = 0,
VertexShader = 1,
FragmentShader = 2,
ComputeShader = 3,
AllShaders = 4,
PreFragmentShaders = 5,
FragmentShadingRate = 6,
Attachment = 7,
AttachmentAndFragmentShader = 8,
AttachmentAndFragmentShaderAndTransfer = 9,
AttachmentAndAllShaders = 10,
TransferAndComputeShader = 11,
// For buffers only
VertexInput = 12,
TransformFeedbackWrite = 13,
InvalidEnum = 14,
EnumCount = InvalidEnum,
};
using EventStageBitMask = typename angle::PackedEnumBitSet<EventStage, uint64_t>;
using EventStageToVkPipelineStageFlagsMap = angle::PackedEnumMap<EventStage, VkPipelineStageFlags>;
// VkCmdWaitEvents requires srcStageMask must be the bitwise OR of the stageMask parameter used in
// previous calls to vkCmdSetEvent (See VUID-vkCmdWaitEvents-srcStageMask-01158). This mean we must
// keep the record of what stageMask each event has been used in VkCmdSetEvent call so that we can
// retrieve that information when we need to wait for the event. Instead of keeping just stageMask
// here, we keep the ImageAccess for now which gives us more information for debugging.
struct EventAndStage
{
bool valid() const { return event.valid(); }
Event event;
EventStage eventStage;
};
// The VkCmdSetEvent is called after VkCmdEndRenderPass and all images that used at the given
// pipeline stage (i.e, they have the same stageMask) will be tracked by the same event. This means
// there will be multiple objects pointing to the same event. Events are thus reference counted so
// that we do not destroy it while other objects still referencing to it.
class RefCountedEvent final
{
public:
RefCountedEvent() { mHandle = nullptr; }
~RefCountedEvent() { ASSERT(mHandle == nullptr); }
// Move constructor moves reference of the underline object from other to this.
RefCountedEvent(RefCountedEvent &&other)
{
mHandle = other.mHandle;
other.mHandle = nullptr;
}
// Copy constructor adds reference to the underline object.
RefCountedEvent(const RefCountedEvent &other)
{
ASSERT(other.valid());
mHandle = other.mHandle;
mHandle->addRef();
}
// Move assignment moves reference of the underline object from other to this.
RefCountedEvent &operator=(RefCountedEvent &&other)
{
ASSERT(!valid());
ASSERT(other.valid());
std::swap(mHandle, other.mHandle);
return *this;
}
// Copy assignment adds reference to the underline object.
RefCountedEvent &operator=(const RefCountedEvent &other)
{
ASSERT(!valid());
ASSERT(other.valid());
mHandle = other.mHandle;
mHandle->addRef();
return *this;
}
// Returns true if both points to the same underline object.
bool operator==(const RefCountedEvent &other) const { return mHandle == other.mHandle; }
// Create VkEvent and associated it with given layout. Returns true if success and false if
// failed.
bool init(Context *context, EventStage eventStage);
// Release one reference count to the underline Event object and destroy or recycle the handle
// to renderer's recycler if this is the very last reference.
void release(Renderer *renderer);
// Release one reference count to the underline Event object and destroy or recycle the handle
// to the context share group's recycler if this is the very last reference.
void release(Context *context);
// Destroy the event and mHandle. Caller must ensure there is no outstanding reference to the
// mHandle.
void destroy(VkDevice device);
bool valid() const { return mHandle != nullptr; }
ANGLE_INLINE void assertValidAndNoReference() const
{
ASSERT(mHandle != nullptr);
mHandle->assertIsRefCountAsExpected(0);
}
// Returns the underlying Event object
const Event &getEvent() const
{
ASSERT(valid());
return mHandle->get().event;
}
EventStage getEventStage() const
{
ASSERT(mHandle != nullptr);
return mHandle->get().eventStage;
}
VkPipelineStageFlags getPipelineStageMask(Renderer *renderer) const;
private:
// Release one reference count to the underline Event object and destroy or recycle the handle
// to the provided recycler if this is the very last reference.
friend class RefCountedEventsGarbage;
template <typename RecyclerT>
void releaseImpl(Renderer *renderer, RecyclerT *recycler);
RefCounted<EventAndStage> *mHandle;
};
using RefCountedEventCollector = std::deque<RefCountedEvent>;
// Tracks a list of RefCountedEvents per EventStage.
class RefCountedEventArray : angle::NonCopyable
{
public:
RefCountedEventArray &operator=(RefCountedEventArray &&other)
{
for (EventStage stage : other.mBitMask)
{
mEvents[stage] = std::move(other.mEvents[stage]);
}
mBitMask = std::move(other.mBitMask);
other.mBitMask.reset();
return *this;
}
void release(Renderer *renderer);
void release(Context *context);
void releaseToEventCollector(RefCountedEventCollector *eventCollector);
const RefCountedEvent &getEvent(EventStage eventStage) const { return mEvents[eventStage]; }
bool initEventAtStage(Context *context, EventStage eventStage);
bool empty() const { return mBitMask.none(); }
const EventStageBitMask getBitMask() const { return mBitMask; }
template <typename CommandBufferT>
void flushSetEvents(Renderer *renderer, CommandBufferT *commandBuffer) const;
protected:
angle::PackedEnumMap<EventStage, RefCountedEvent> mEvents;
// The mask is used to accelerate the loop of map
EventStageBitMask mBitMask;
};
class RefCountedEventArrayWithAccessFlags final : public RefCountedEventArray
{
public:
RefCountedEventArrayWithAccessFlags() { mAccessFlags.fill(0); }
void replaceEventAtStage(Context *context,
EventStage eventStage,
const RefCountedEvent &event,
VkAccessFlags accessFlags)
{
if (mBitMask[eventStage])
{
mEvents[eventStage].release(context);
}
mEvents[eventStage] = event;
mAccessFlags[eventStage] |= accessFlags;
mBitMask.set(eventStage);
}
VkAccessFlags getAccessFlags(EventStage eventStage) const
{
ASSERT(mBitMask[eventStage]);
return mAccessFlags[eventStage];
}
void release(Renderer *renderer)
{
RefCountedEventArray::release(renderer);
mAccessFlags.fill(0);
}
void release(Context *context)
{
RefCountedEventArray::release(context);
mAccessFlags.fill(0);
}
void releaseToEventCollector(RefCountedEventCollector *eventCollector)
{
for (EventStage eventStage : mBitMask)
{
eventCollector->emplace_back(std::move(mEvents[eventStage]));
mAccessFlags[eventStage] = 0;
}
mBitMask.reset();
}
bool hasEventAndAccess(EventStage eventStage, VkAccessFlags accessType) const
{
return mBitMask.test(eventStage) && (mAccessFlags[eventStage] & accessType) == accessType;
}
private:
angle::PackedEnumMap<EventStage, VkAccessFlags> mAccessFlags;
};
class RefCountedEventWithAccessFlags final
{
public:
RefCountedEventWithAccessFlags() : mAccessFlags(0) {}
void release(Renderer *renderer)
{
mEvent.release(renderer);
mAccessFlags = 0;
}
void release(Context *context)
{
mEvent.release(context);
mAccessFlags = 0;
}
void releaseToEventCollector(RefCountedEventCollector *eventCollector)
{
eventCollector->emplace_back(std::move(mEvent));
mAccessFlags = 0;
}
RefCountedEventWithAccessFlags &operator=(RefCountedEventWithAccessFlags &&other)
{
mEvent = std::move(other.mEvent);
mAccessFlags = other.mAccessFlags;
other.mAccessFlags = 0;
return *this;
}
void setEventAndAccessFlags(const RefCountedEvent &event, VkAccessFlags accessFlags)
{
mEvent = event;
mAccessFlags = accessFlags;
}
const RefCountedEvent &getEvent() const { return mEvent; }
VkAccessFlags getAccessFlags() const
{
ASSERT(mEvent.valid());
return mAccessFlags;
}
bool valid() const { return mEvent.valid(); }
EventStage getEventStage() const { return mEvent.getEventStage(); }
private:
RefCountedEvent mEvent;
VkAccessFlags mAccessFlags;
};
// Only used by RenderPassCommandBufferHelper
class EventArray final : angle::NonCopyable
{
public:
void init(Renderer *renderer, const RefCountedEventArray &refCountedEventArray);
bool empty() const { return mBitMask.none(); }
void flushSetEvents(PrimaryCommandBuffer *primary);
private:
// The mask is used to accelerate the loop of map
EventStageBitMask mBitMask;
angle::PackedEnumMap<EventStage, VkEvent> mEvents;
angle::PackedEnumMap<EventStage, VkPipelineStageFlags> mPipelineStageFlags;
};
// This class tracks a vector of RefcountedEvent garbage. For performance reason, instead of
// individually tracking each VkEvent garbage, we collect all events that are accessed in the
// CommandBufferHelper into this class. After we submit the command buffer, we treat this vector of
// events as one garbage object and add it to renderer's garbage list. The garbage clean up will
// decrement the refCount and destroy event only when last refCount goes away. Basically all GPU
// usage will use one refCount and that refCount ensures we never destroy event until GPU is
// finished.
class RefCountedEventsGarbage final
{
public:
RefCountedEventsGarbage() = default;
~RefCountedEventsGarbage() { ASSERT(mRefCountedEvents.empty()); }
RefCountedEventsGarbage(const QueueSerial &queueSerial,
RefCountedEventCollector &&refCountedEvents)
: mQueueSerial(queueSerial), mRefCountedEvents(std::move(refCountedEvents))
{
ASSERT(!mRefCountedEvents.empty());
}
void destroy(Renderer *renderer);
// Check the queue serial and release the events to recycler if GPU finished.
bool releaseIfComplete(Renderer *renderer, RefCountedEventsGarbageRecycler *recycler);
// Check the queue serial and move all events to releasedBucket if GPU finished. This is only
// used by RefCountedEventRecycler.
bool moveIfComplete(Renderer *renderer, std::deque<RefCountedEventCollector> *releasedBucket);
bool empty() const { return mRefCountedEvents.empty(); }
size_t size() const { return mRefCountedEvents.size(); }
private:
QueueSerial mQueueSerial;
RefCountedEventCollector mRefCountedEvents;
};
// Two levels of RefCountedEvents recycle system: For the performance reason, we have two levels of
// events recycler system. The first level is per ShareGroupVk, which owns
// RefCountedEventsGarbageRecycler. RefCountedEvent garbage is added to it without any lock. Once
// GPU complete, the refCount is decremented. When the last refCount goes away, it goes into
// mEventsToReset. Note that since ShareGroupVk access is already protected by context share lock at
// the API level, so no lock is taken and reference counting is not atomic. At
// RefCountedEventsGarbageRecycler::cleanup time, the entire mEventsToReset is added into renderer's
// list. The renderer owns RefCountedEventRecycler list, and all access to it is protected with
// simple mutex lock. When any context calls OutsideRenderPassCommandBufferHelper::flushToPrimary,
// mEventsToReset is retrieved from renderer and the reset commands is added to the command buffer.
// The events are then moved to the renderer's garbage list. They are checked and along with
// renderer's garbage cleanup and if completed, they get moved to renderer's mEventsToReuse list.
// When a RefCountedEvent is needed, we always dip into ShareGroupVk's mEventsToReuse list. If its
// empty, it then dip into renderer's mEventsToReuse and grab a collector of events and try to
// reuse. That way the traffic into renderer is minimized as most of calls will be contained in
// ShareGroupVk.
// Thread safe event recycler, protected by its own lock.
class RefCountedEventRecycler final
{
public:
RefCountedEventRecycler() {}
~RefCountedEventRecycler()
{
ASSERT(mEventsToReset.empty());
ASSERT(mResettingQueue.empty());
ASSERT(mEventsToReuse.empty());
}
void destroy(VkDevice device);
// Add single event to the toReset list
void recycle(RefCountedEvent &&garbageObject, VkDevice device)
{
garbageObject.assertValidAndNoReference();
std::lock_guard<angle::SimpleMutex> lock(mMutex);
if (mEventsToReset.size() >= kMaxEventToKeepCount)
{
garbageObject.destroy(device);
}
else
{
if (mEventsToReset.empty())
{
mEventsToReset.emplace_back();
}
mEventsToReset.back().emplace_back(std::move(garbageObject));
}
}
// Add a list of events to the toReset list
void recycle(RefCountedEventCollector &&garbageObjects, VkDevice device);
// Reset all events in the toReset list and move them to the toReuse list
void resetEvents(ErrorContext *context,
const QueueSerial queueSerial,
PrimaryCommandBuffer *commandbuffer);
// Clean up the resetting event list and move completed events to the toReuse list.
// Number of events released is returned.
size_t cleanupResettingEvents(Renderer *renderer);
// Fetch a list of events that are ready to be reused. Returns true if eventsToReuseOut is
// returned.
bool fetchEventsToReuse(RefCountedEventCollector *eventsToReuseOut);
private:
// Maximum number of RefCountedEventCollector we will keep in mEventsToReset.
// When mEventsToReset size reaches kMaxEventToKeepCount, any extra used VkEvent items will be
// destroyed immediately.
static constexpr size_t kMaxEventToKeepCount = 16;
angle::SimpleMutex mMutex;
// RefCountedEvent list that has been released, needs to be reset.
std::deque<RefCountedEventCollector> mEventsToReset;
// RefCountedEvent list that is currently resetting.
std::queue<RefCountedEventsGarbage> mResettingQueue;
// RefCountedEvent list that already has been reset. Ready to be reused.
std::deque<RefCountedEventCollector> mEventsToReuse;
};
// Not thread safe event garbage collection and recycler. Caller must ensure the thread safety. It
// is intended to use by ShareGroupVk which all access should already protected by share context
// lock.
class RefCountedEventsGarbageRecycler final
{
public:
RefCountedEventsGarbageRecycler() : mGarbageCount(0) {}
~RefCountedEventsGarbageRecycler();
// Release all garbage and free events.
void destroy(Renderer *renderer);
// Walk the garbage list and move completed garbage to free list
void cleanup(Renderer *renderer);
void collectGarbage(const QueueSerial &queueSerial, RefCountedEventCollector &&refCountedEvents)
{
mGarbageCount += refCountedEvents.size();
mGarbageQueue.emplace(queueSerial, std::move(refCountedEvents));
}
void recycle(RefCountedEvent &&garbageObject, VkDevice device)
{
garbageObject.assertValidAndNoReference();
mEventsToReset.emplace_back(std::move(garbageObject));
}
bool fetch(Renderer *renderer, RefCountedEvent *outObject);
size_t getGarbageCount() const { return mGarbageCount; }
private:
RefCountedEventCollector mEventsToReset;
std::queue<RefCountedEventsGarbage> mGarbageQueue;
Recycler<RefCountedEvent> mEventsToReuse;
size_t mGarbageCount;
};
// This wraps data and API for vkCmdWaitEvent call
class EventBarrier : angle::NonCopyable
{
public:
EventBarrier()
: mSrcStageMask(0),
mDstStageMask(0),
mMemoryBarrierSrcAccess(0),
mMemoryBarrierDstAccess(0),
mImageMemoryBarrierCount(0),
mEvent(VK_NULL_HANDLE)
{}
EventBarrier(VkPipelineStageFlags srcStageMask,
VkPipelineStageFlags dstStageMask,
VkAccessFlags srcAccess,
VkAccessFlags dstAccess,
const VkEvent &event)
: mSrcStageMask(srcStageMask),
mDstStageMask(dstStageMask),
mMemoryBarrierSrcAccess(srcAccess),
mMemoryBarrierDstAccess(dstAccess),
mImageMemoryBarrierCount(0),
mEvent(event)
{
ASSERT(mEvent != VK_NULL_HANDLE);
}
EventBarrier(VkPipelineStageFlags srcStageMask,
VkPipelineStageFlags dstStageMask,
const VkEvent &event,
const VkImageMemoryBarrier &imageMemoryBarrier)
: mSrcStageMask(srcStageMask),
mDstStageMask(dstStageMask),
mMemoryBarrierSrcAccess(0),
mMemoryBarrierDstAccess(0),
mImageMemoryBarrierCount(1),
mEvent(event),
mImageMemoryBarrier(imageMemoryBarrier)
{
ASSERT(mEvent != VK_NULL_HANDLE);
ASSERT(mImageMemoryBarrier.image != VK_NULL_HANDLE);
ASSERT(mImageMemoryBarrier.pNext == nullptr);
}
EventBarrier(EventBarrier &&other)
{
mSrcStageMask = other.mSrcStageMask;
mDstStageMask = other.mDstStageMask;
mMemoryBarrierSrcAccess = other.mMemoryBarrierSrcAccess;
mMemoryBarrierDstAccess = other.mMemoryBarrierDstAccess;
mImageMemoryBarrierCount = other.mImageMemoryBarrierCount;
std::swap(mEvent, other.mEvent);
std::swap(mImageMemoryBarrier, other.mImageMemoryBarrier);
other.mSrcStageMask = 0;
other.mDstStageMask = 0;
other.mMemoryBarrierSrcAccess = 0;
other.mMemoryBarrierDstAccess = 0;
other.mImageMemoryBarrierCount = 0;
}
~EventBarrier() {}
bool isEmpty() const { return mEvent == VK_NULL_HANDLE; }
bool hasEvent(const VkEvent &event) const { return mEvent == event; }
void addAdditionalStageAccess(VkPipelineStageFlags dstStageMask, VkAccessFlags dstAccess)
{
mDstStageMask |= dstStageMask;
mMemoryBarrierDstAccess |= dstAccess;
}
void execute(PrimaryCommandBuffer *primary);
void addDiagnosticsString(std::ostringstream &out) const;
private:
friend class EventBarrierArray;
VkPipelineStageFlags mSrcStageMask;
VkPipelineStageFlags mDstStageMask;
VkAccessFlags mMemoryBarrierSrcAccess;
VkAccessFlags mMemoryBarrierDstAccess;
uint32_t mImageMemoryBarrierCount;
VkEvent mEvent;
VkImageMemoryBarrier mImageMemoryBarrier;
};
class EventBarrierArray final
{
public:
bool isEmpty() const { return mBarriers.empty(); }
void execute(Renderer *renderer, PrimaryCommandBuffer *primary);
// Add the additional stageMask to the existing waitEvent.
void addAdditionalStageAccess(const RefCountedEvent &waitEvent,
VkPipelineStageFlags dstStageMask,
VkAccessFlags dstAccess);
void addEventMemoryBarrier(Renderer *renderer,
const RefCountedEvent &waitEvent,
VkAccessFlags srcAccess,
VkPipelineStageFlags dstStageMask,
VkAccessFlags dstAccess);
void addEventImageBarrier(Renderer *renderer,
const RefCountedEvent &waitEvent,
VkPipelineStageFlags dstStageMask,
const VkImageMemoryBarrier &imageMemoryBarrier);
void reset() { ASSERT(mBarriers.empty()); }
void addDiagnosticsString(std::ostringstream &out) const;
private:
std::deque<EventBarrier> mBarriers;
};
} // namespace vk
} // namespace rx
#endif // LIBANGLE_RENDERER_VULKAN_REFCOUNTED_EVENT_H_