blob: e5b7831df7857d056715b0abfbb3f0d9092dfaec [file] [log] [blame]
//
// 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.
//
// CommandQueue.h:
// A class to process and submit Vulkan command buffers.
//
#ifndef LIBANGLE_RENDERER_VULKAN_COMMAND_Queue_H_
#define LIBANGLE_RENDERER_VULKAN_COMMAND_Queue_H_
#include <condition_variable>
#include <mutex>
#include <queue>
#include <thread>
#include "common/FixedQueue.h"
#include "common/SimpleMutex.h"
#include "common/vulkan/vk_headers.h"
#include "libANGLE/renderer/vulkan/PersistentCommandPool.h"
#include "libANGLE/renderer/vulkan/vk_helpers.h"
#include "vulkan/vulkan_core.h"
namespace rx
{
namespace vk
{
class ExternalFence;
using SharedExternalFence = std::shared_ptr<ExternalFence>;
constexpr size_t kInFlightCommandsLimit = 50u;
constexpr size_t kMaxFinishedCommandsLimit = 64u;
static_assert(kInFlightCommandsLimit <= kMaxFinishedCommandsLimit);
struct Error
{
VkResult errorCode;
const char *file;
const char *function;
uint32_t line;
};
class FenceRecycler
{
public:
FenceRecycler() {}
~FenceRecycler() {}
void destroy(ErrorContext *context);
void fetch(VkDevice device, Fence *fenceOut);
void recycle(Fence &&fence);
private:
angle::SimpleMutex mMutex;
Recycler<Fence> mRecycler;
};
class RecyclableFence final : angle::NonCopyable
{
public:
RecyclableFence();
~RecyclableFence();
VkResult init(VkDevice device, FenceRecycler *recycler);
// Returns fence back to the recycler if it is still attached, destroys the fence otherwise.
// Do NOT call directly when object is controlled by a shared pointer.
void destroy(VkDevice device);
void detachRecycler() { mRecycler = nullptr; }
bool valid() const { return mFence.valid(); }
const Fence &get() const { return mFence; }
private:
Fence mFence;
FenceRecycler *mRecycler;
};
using SharedFence = AtomicSharedPtr<RecyclableFence>;
class CommandPoolAccess;
class CommandBatch final : angle::NonCopyable
{
public:
CommandBatch();
~CommandBatch();
CommandBatch(CommandBatch &&other);
CommandBatch &operator=(CommandBatch &&other);
void destroy(VkDevice device);
angle::Result release(ErrorContext *context);
void setQueueSerial(const QueueSerial &serial);
void setProtectionType(ProtectionType protectionType);
void setPrimaryCommands(PrimaryCommandBuffer &&primaryCommands,
CommandPoolAccess *commandPoolAccess);
void setSecondaryCommands(SecondaryCommandBufferCollector &&secondaryCommands);
VkResult initFence(VkDevice device, FenceRecycler *recycler);
void setExternalFence(SharedExternalFence &&externalFence);
const QueueSerial &getQueueSerial() const;
const PrimaryCommandBuffer &getPrimaryCommands() const;
const SharedExternalFence &getExternalFence();
bool hasFence() const;
VkFence getFenceHandle() const;
VkResult getFenceStatus(VkDevice device) const;
VkResult waitFence(VkDevice device, uint64_t timeout) const;
VkResult waitFenceUnlocked(VkDevice device,
uint64_t timeout,
std::unique_lock<angle::SimpleMutex> *lock) const;
private:
QueueSerial mQueueSerial;
ProtectionType mProtectionType;
PrimaryCommandBuffer mPrimaryCommands;
CommandPoolAccess *mCommandPoolAccess; // reference to CommandPoolAccess that is responsible
// for deleting primaryCommands with a lock
SecondaryCommandBufferCollector mSecondaryCommands;
SharedFence mFence;
SharedExternalFence mExternalFence;
};
using CommandBatchQueue = angle::FixedQueue<CommandBatch>;
class DeviceQueueMap;
class QueueFamily final : angle::NonCopyable
{
public:
static const uint32_t kInvalidIndex = std::numeric_limits<uint32_t>::max();
static uint32_t FindIndex(const std::vector<VkQueueFamilyProperties> &queueFamilyProperties,
VkQueueFlags includeFlags,
VkQueueFlags optionalFlags,
VkQueueFlags excludeFlags,
uint32_t *matchCount);
static const uint32_t kQueueCount = static_cast<uint32_t>(egl::ContextPriority::EnumCount);
static const float kQueuePriorities[static_cast<uint32_t>(egl::ContextPriority::EnumCount)];
QueueFamily() : mProperties{}, mQueueFamilyIndex(kInvalidIndex) {}
~QueueFamily() {}
void initialize(const VkQueueFamilyProperties &queueFamilyProperties,
uint32_t queueFamilyIndex);
bool valid() const { return (mQueueFamilyIndex != kInvalidIndex); }
uint32_t getQueueFamilyIndex() const { return mQueueFamilyIndex; }
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; }
private:
VkQueueFamilyProperties mProperties;
uint32_t mQueueFamilyIndex;
};
class DeviceQueueMap final
{
public:
DeviceQueueMap() : mQueueFamilyIndex(QueueFamily::kInvalidIndex), mIsProtected(false) {}
~DeviceQueueMap();
void initialize(VkDevice device,
const QueueFamily &queueFamily,
bool makeProtected,
uint32_t queueIndex,
uint32_t queueCount);
void destroy();
bool valid() const { return (mQueueFamilyIndex != QueueFamily::kInvalidIndex); }
uint32_t getQueueFamilyIndex() const { return mQueueFamilyIndex; }
bool isProtected() const { return mIsProtected; }
egl::ContextPriority getDevicePriority(egl::ContextPriority priority) const
{
return mQueueAndIndices[priority].devicePriority;
}
DeviceQueueIndex getDeviceQueueIndex(egl::ContextPriority priority) const
{
return DeviceQueueIndex(mQueueFamilyIndex, mQueueAndIndices[priority].index);
}
const VkQueue &getQueue(egl::ContextPriority priority) const
{
return mQueueAndIndices[priority].queue;
}
// Wait for all queues to be idle, called on device loss and destruction.
void waitAllQueuesIdle();
private:
uint32_t mQueueFamilyIndex;
bool mIsProtected;
struct QueueAndIndex
{
// The actual priority that used
egl::ContextPriority devicePriority;
VkQueue queue;
// The queueIndex used for VkGetDeviceQueue
uint32_t index;
};
angle::PackedEnumMap<egl::ContextPriority, QueueAndIndex> mQueueAndIndices;
};
class CommandPoolAccess : angle::NonCopyable
{
public:
CommandPoolAccess();
~CommandPoolAccess();
angle::Result initCommandPool(ErrorContext *context,
ProtectionType protectionType,
const uint32_t queueFamilyIndex);
void destroy(VkDevice device);
void destroyPrimaryCommandBuffer(VkDevice device, PrimaryCommandBuffer *primaryCommands) const;
angle::Result collectPrimaryCommandBuffer(ErrorContext *context,
const ProtectionType protectionType,
PrimaryCommandBuffer *primaryCommands);
angle::Result flushOutsideRPCommands(Context *context,
ProtectionType protectionType,
egl::ContextPriority priority,
OutsideRenderPassCommandBufferHelper **outsideRPCommands);
angle::Result flushRenderPassCommands(Context *context,
const ProtectionType &protectionType,
const egl::ContextPriority &priority,
const RenderPass &renderPass,
VkFramebuffer framebufferOverride,
RenderPassCommandBufferHelper **renderPassCommands);
void flushWaitSemaphores(ProtectionType protectionType,
egl::ContextPriority priority,
std::vector<VkSemaphore> &&waitSemaphores,
std::vector<VkPipelineStageFlags> &&waitSemaphoreStageMasks);
angle::Result getCommandsAndWaitSemaphores(
ErrorContext *context,
ProtectionType protectionType,
egl::ContextPriority priority,
CommandBatch *batchOut,
std::vector<VkImageMemoryBarrier> &&imagesToTransitionToForeign,
std::vector<VkSemaphore> *waitSemaphoresOut,
std::vector<VkPipelineStageFlags> *waitSemaphoreStageMasksOut);
private:
angle::Result ensurePrimaryCommandBufferValidLocked(ErrorContext *context,
const ProtectionType &protectionType,
const egl::ContextPriority &priority)
{
CommandsState &state = mCommandsStateMap[priority][protectionType];
if (state.primaryCommands.valid())
{
return angle::Result::Continue;
}
ANGLE_TRY(mPrimaryCommandPoolMap[protectionType].allocate(context, &state.primaryCommands));
VkCommandBufferBeginInfo beginInfo = {};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
beginInfo.pInheritanceInfo = nullptr;
ANGLE_VK_TRY(context, state.primaryCommands.begin(beginInfo));
return angle::Result::Continue;
}
// This mutex ensures vulkan command pool is externally synchronized.
// This means no two threads are operating on command buffers allocated from
// the same command pool at the same time. The operations that this mutex
// protect include:
// 1) recording commands on any command buffers allocated from the same command pool
// 2) allocate, free, reset command buffers from the same command pool.
// 3) any operations on the command pool itself
mutable angle::SimpleMutex mCmdPoolMutex;
using PrimaryCommandPoolMap = angle::PackedEnumMap<ProtectionType, PersistentCommandPool>;
using CommandsStateMap =
angle::PackedEnumMap<egl::ContextPriority,
angle::PackedEnumMap<ProtectionType, CommandsState>>;
CommandsStateMap mCommandsStateMap;
// Keeps a free list of reusable primary command buffers.
PrimaryCommandPoolMap mPrimaryCommandPoolMap;
};
// Note all public APIs of CommandQueue class must be thread safe.
class CommandQueue : angle::NonCopyable
{
public:
CommandQueue();
~CommandQueue();
angle::Result init(ErrorContext *context,
const QueueFamily &queueFamily,
bool enableProtectedContent,
uint32_t queueCount);
void destroy(ErrorContext *context);
void handleDeviceLost(Renderer *renderer);
// These public APIs are inherently thread safe. Thread unsafe methods must be protected methods
// that are only accessed via ThreadSafeCommandQueue API.
egl::ContextPriority getDriverPriority(egl::ContextPriority priority) const
{
return mQueueMap.getDevicePriority(priority);
}
DeviceQueueIndex getDeviceQueueIndex(egl::ContextPriority priority) const
{
return mQueueMap.getDeviceQueueIndex(priority);
}
VkQueue getQueue(egl::ContextPriority priority) const { return mQueueMap.getQueue(priority); }
// The following are used to implement EGL_ANGLE_device_vulkan, and are called by the
// application when it wants to access the VkQueue previously retrieved from ANGLE. Do not call
// these for synchronization within ANGLE.
void lockVulkanQueueForExternalAccess() { mQueueSubmitMutex.lock(); }
void unlockVulkanQueueForExternalAccess() { mQueueSubmitMutex.unlock(); }
Serial getLastSubmittedSerial(SerialIndex index) const { return mLastSubmittedSerials[index]; }
// The ResourceUse still have unfinished queue serial by ANGLE or vulkan.
bool hasResourceUseFinished(const ResourceUse &use) const
{
return use <= mLastCompletedSerials;
}
bool hasQueueSerialFinished(const QueueSerial &queueSerial) const
{
return queueSerial <= mLastCompletedSerials;
}
// The ResourceUse still have queue serial not yet submitted to vulkan.
bool hasResourceUseSubmitted(const ResourceUse &use) const
{
return use <= mLastSubmittedSerials;
}
bool hasQueueSerialSubmitted(const QueueSerial &queueSerial) const
{
return queueSerial <= mLastSubmittedSerials;
}
// Wait until the desired serial has been completed.
angle::Result finishResourceUse(ErrorContext *context,
const ResourceUse &use,
uint64_t timeout);
angle::Result finishQueueSerial(ErrorContext *context,
const QueueSerial &queueSerial,
uint64_t timeout);
angle::Result waitIdle(ErrorContext *context, uint64_t timeout);
angle::Result waitForResourceUseToFinishWithUserTimeout(ErrorContext *context,
const ResourceUse &use,
uint64_t timeout,
VkResult *result);
bool isBusy(Renderer *renderer) const;
angle::Result submitCommands(ErrorContext *context,
ProtectionType protectionType,
egl::ContextPriority priority,
VkSemaphore signalSemaphore,
SharedExternalFence &&externalFence,
std::vector<VkImageMemoryBarrier> &&imagesToTransitionToForeign,
const QueueSerial &submitQueueSerial);
angle::Result queueSubmitOneOff(ErrorContext *context,
ProtectionType protectionType,
egl::ContextPriority contextPriority,
VkCommandBuffer commandBufferHandle,
VkSemaphore waitSemaphore,
VkPipelineStageFlags waitSemaphoreStageMask,
const QueueSerial &submitQueueSerial);
// Note: Some errors from present are not fatal.
VkResult queuePresent(egl::ContextPriority contextPriority,
const VkPresentInfoKHR &presentInfo);
angle::Result checkCompletedCommands(ErrorContext *context)
{
std::lock_guard<angle::SimpleMutex> lock(mCmdCompleteMutex);
return checkCompletedCommandsLocked(context);
}
bool hasFinishedCommands() const { return !mFinishedCommandBatches.empty(); }
angle::Result checkAndCleanupCompletedCommands(ErrorContext *context)
{
ANGLE_TRY(checkCompletedCommands(context));
if (!mFinishedCommandBatches.empty())
{
ANGLE_TRY(releaseFinishedCommandsAndCleanupGarbage(context));
}
return angle::Result::Continue;
}
ANGLE_INLINE void flushWaitSemaphores(
ProtectionType protectionType,
egl::ContextPriority priority,
std::vector<VkSemaphore> &&waitSemaphores,
std::vector<VkPipelineStageFlags> &&waitSemaphoreStageMasks)
{
return mCommandPoolAccess.flushWaitSemaphores(protectionType, priority,
std::move(waitSemaphores),
std::move(waitSemaphoreStageMasks));
}
ANGLE_INLINE angle::Result flushOutsideRPCommands(
Context *context,
ProtectionType protectionType,
egl::ContextPriority priority,
OutsideRenderPassCommandBufferHelper **outsideRPCommands)
{
return mCommandPoolAccess.flushOutsideRPCommands(context, protectionType, priority,
outsideRPCommands);
}
ANGLE_INLINE angle::Result flushRenderPassCommands(
Context *context,
ProtectionType protectionType,
const egl::ContextPriority &priority,
const RenderPass &renderPass,
VkFramebuffer framebufferOverride,
RenderPassCommandBufferHelper **renderPassCommands)
{
return mCommandPoolAccess.flushRenderPassCommands(
context, protectionType, priority, renderPass, framebufferOverride, renderPassCommands);
}
const angle::VulkanPerfCounters getPerfCounters() const;
void resetPerFramePerfCounters();
// Release finished commands and clean up garbage immediately, or request async clean up if
// enabled.
angle::Result releaseFinishedCommandsAndCleanupGarbage(ErrorContext *context);
angle::Result releaseFinishedCommands(ErrorContext *context)
{
std::lock_guard<angle::SimpleMutex> lock(mCmdReleaseMutex);
return releaseFinishedCommandsLocked(context);
}
angle::Result postSubmitCheck(ErrorContext *context);
bool isInFlightCommandsEmpty() const;
// Try to cleanup garbage and return if something was cleaned. Otherwise, wait for the
// mInFlightCommands and retry.
angle::Result cleanupSomeGarbage(ErrorContext *context,
size_t minInFlightBatchesToKeep,
bool *anyGarbageCleanedOut);
// All these private APIs are called with mutex locked, so we must not take lock again.
private:
// Check the first command buffer in mInFlightCommands and update mLastCompletedSerials if
// finished
angle::Result checkOneCommandBatchLocked(ErrorContext *context, bool *finished);
// Similar to checkOneCommandBatch, except we will wait for it to finish
angle::Result finishOneCommandBatch(ErrorContext *context,
uint64_t timeout,
std::unique_lock<angle::SimpleMutex> *lock);
void onCommandBatchFinishedLocked(CommandBatch &&batch);
// Walk mFinishedCommands, reset and recycle all command buffers.
angle::Result releaseFinishedCommandsLocked(ErrorContext *context);
// Walk mInFlightCommands, check and update mLastCompletedSerials for all commands that are
// finished
angle::Result checkCompletedCommandsLocked(ErrorContext *context);
angle::Result queueSubmitLocked(ErrorContext *context,
egl::ContextPriority contextPriority,
const VkSubmitInfo &submitInfo,
DeviceScoped<CommandBatch> &commandBatch,
const QueueSerial &submitQueueSerial);
void pushInFlightBatchLocked(CommandBatch &&batch);
void moveInFlightBatchToFinishedQueueLocked(CommandBatch &&batch);
void popFinishedBatchLocked();
void popInFlightBatchLocked();
CommandPoolAccess mCommandPoolAccess;
// Warning: Mutexes must be locked in the order as declared below.
// Protect multi-thread access to mInFlightCommands.push/back and ensure ordering of submission.
// Also protects mPerfCounters.
mutable angle::SimpleMutex mQueueSubmitMutex;
// Protect multi-thread access to mInFlightCommands.pop/front and
// mFinishedCommandBatches.push/back.
angle::SimpleMutex mCmdCompleteMutex;
// Protect multi-thread access to mFinishedCommandBatches.pop/front.
angle::SimpleMutex mCmdReleaseMutex;
CommandBatchQueue mInFlightCommands;
// Temporary storage for finished command batches that should be reset.
CommandBatchQueue mFinishedCommandBatches;
// Combined number of batches in mInFlightCommands and mFinishedCommandBatches queues.
// Used instead of calculating the sum because doing this is not thread safe and will require
// the mCmdCompleteMutex lock.
std::atomic_size_t mNumAllCommands;
// Queue serial management.
AtomicQueueSerialFixedArray mLastSubmittedSerials;
// This queue serial can be read/write from different threads, so we need to use atomic
// operations to access the underlying value. Since we only do load/store on this value, it
// should be just a normal uint64_t load/store on most platforms.
AtomicQueueSerialFixedArray mLastCompletedSerials;
// QueueMap
DeviceQueueMap mQueueMap;
FenceRecycler mFenceRecycler;
angle::VulkanPerfCounters mPerfCounters;
};
ANGLE_INLINE bool CommandQueue::isInFlightCommandsEmpty() const
{
return mInFlightCommands.empty();
}
// A helper thread used to clean up garbage
class CleanUpThread : public ErrorContext
{
public:
CleanUpThread(Renderer *renderer, CommandQueue *commandQueue);
~CleanUpThread() override;
// Context
void handleError(VkResult result,
const char *file,
const char *function,
unsigned int line) override;
angle::Result init();
void destroy(ErrorContext *context);
void requestCleanUp();
std::thread::id getThreadId() const { return mTaskThread.get_id(); }
private:
bool hasPendingError() const
{
std::lock_guard<angle::SimpleMutex> queueLock(mErrorMutex);
return !mErrors.empty();
}
angle::Result checkAndPopPendingError(ErrorContext *errorHandlingContext);
// Entry point for clean up thread, calls processTasksImpl to do the
// work. called by Renderer::initializeDevice on main thread
void processTasks();
// Clean up thread, called by processTasks. The loop waits for work to
// be submitted from a separate thread.
angle::Result processTasksImpl(bool *exitThread);
CommandQueue *const mCommandQueue;
mutable angle::SimpleMutex mErrorMutex;
std::queue<Error> mErrors;
// Command queue worker thread.
std::thread mTaskThread;
bool mTaskThreadShouldExit;
std::mutex mMutex;
std::condition_variable mWorkAvailableCondition;
std::atomic<bool> mNeedCleanUp;
};
// Provides access to the PrimaryCommandBuffer while also locking the corresponding CommandPool
class [[nodiscard]] ScopedPrimaryCommandBuffer final
{
public:
explicit ScopedPrimaryCommandBuffer(VkDevice device) : mCommandBuffer(device) {}
void assign(std::unique_lock<angle::SimpleMutex> &&poolLock,
PrimaryCommandBuffer &&commandBuffer)
{
ASSERT(poolLock.owns_lock());
ASSERT(commandBuffer.valid());
ASSERT(mPoolLock.mutex() == nullptr);
ASSERT(!mCommandBuffer.get().valid());
mPoolLock = std::move(poolLock);
mCommandBuffer.get() = std::move(commandBuffer);
}
PrimaryCommandBuffer &get()
{
ASSERT(mPoolLock.owns_lock());
ASSERT(mCommandBuffer.get().valid());
return mCommandBuffer.get();
}
DeviceScoped<PrimaryCommandBuffer> unlockAndRelease()
{
ASSERT(mCommandBuffer.get().valid() && mPoolLock.owns_lock() ||
!mCommandBuffer.get().valid() && mPoolLock.mutex() == nullptr);
mPoolLock = {};
return std::move(mCommandBuffer);
}
private:
std::unique_lock<angle::SimpleMutex> mPoolLock;
DeviceScoped<PrimaryCommandBuffer> mCommandBuffer;
};
} // namespace vk
} // namespace rx
#endif // LIBANGLE_RENDERER_VULKAN_COMMAND_QUEUE_H_