[dxvk] Use global timeline semaphore for command list synchronization

Replaces the old fence mechanism and also makes it easier to
synchronize across queues.
This commit is contained in:
Philip Rebohle 2022-08-22 00:32:49 +02:00
parent cff9056915
commit f385b4bb47
4 changed files with 73 additions and 71 deletions

View File

@ -11,14 +11,6 @@ namespace dxvk {
const auto& graphicsQueue = m_device->queues().graphics;
const auto& transferQueue = m_device->queues().transfer;
VkFenceCreateInfo fenceInfo;
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.pNext = nullptr;
fenceInfo.flags = 0;
if (m_vkd->vkCreateFence(m_vkd->device(), &fenceInfo, nullptr, &m_fence) != VK_SUCCESS)
throw DxvkError("DxvkCommandList: Failed to create fence");
VkCommandPoolCreateInfo poolInfo;
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.pNext = nullptr;
@ -53,32 +45,20 @@ namespace dxvk {
|| m_vkd->vkAllocateCommandBuffers(m_vkd->device(), &cmdInfoGfx, &m_initBuffer) != VK_SUCCESS
|| m_vkd->vkAllocateCommandBuffers(m_vkd->device(), &cmdInfoDma, &m_sdmaBuffer) != VK_SUCCESS)
throw DxvkError("DxvkCommandList: Failed to allocate command buffer");
if (m_device->hasDedicatedTransferQueue()) {
VkSemaphoreCreateInfo semInfo;
semInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
semInfo.pNext = nullptr;
semInfo.flags = 0;
if (m_vkd->vkCreateSemaphore(m_vkd->device(), &semInfo, nullptr, &m_sdmaSemaphore) != VK_SUCCESS)
throw DxvkError("DxvkCommandList: Failed to create semaphore");
}
}
DxvkCommandList::~DxvkCommandList() {
this->reset();
m_vkd->vkDestroySemaphore(m_vkd->device(), m_sdmaSemaphore, nullptr);
m_vkd->vkDestroyCommandPool(m_vkd->device(), m_graphicsPool, nullptr);
m_vkd->vkDestroyCommandPool(m_vkd->device(), m_transferPool, nullptr);
m_vkd->vkDestroyFence(m_vkd->device(), m_fence, nullptr);
}
VkResult DxvkCommandList::submit() {
VkResult DxvkCommandList::submit(
VkSemaphore semaphore,
uint64_t& semaphoreValue) {
const auto& graphics = m_device->queues().graphics;
const auto& transfer = m_device->queues().transfer;
@ -91,11 +71,12 @@ namespace dxvk {
if (m_device->hasDedicatedTransferQueue()) {
VkSemaphoreSubmitInfo signalInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO };
signalInfo.semaphore = m_sdmaSemaphore;
signalInfo.semaphore = semaphore;
signalInfo.value = ++semaphoreValue;
signalInfo.stageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT;
m_submission.signalInfos.push_back(signalInfo);
VkResult status = submitToQueue(transfer.queueHandle, VK_NULL_HANDLE, m_submission);
VkResult status = submitToQueue(transfer.queueHandle, m_submission);
if (status != VK_SUCCESS)
return status;
@ -103,7 +84,8 @@ namespace dxvk {
m_submission.reset();
VkSemaphoreSubmitInfo waitInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO };
waitInfo.semaphore = m_sdmaSemaphore;
waitInfo.semaphore = semaphore;
waitInfo.value = semaphoreValue;
waitInfo.stageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT;
m_submission.waitInfos.push_back(waitInfo);
}
@ -151,20 +133,14 @@ namespace dxvk {
m_submission.signalInfos.push_back(signalInfo);
}
return submitToQueue(graphics.queueHandle, m_fence, m_submission);
}
VkResult DxvkCommandList::synchronize() {
VkResult status = VK_TIMEOUT;
while (status == VK_TIMEOUT) {
status = m_vkd->vkWaitForFences(
m_vkd->device(), 1, &m_fence, VK_FALSE,
1'000'000'000ull);
}
return status;
// Signal global timeline semaphore
VkSemaphoreSubmitInfo signalInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO };
signalInfo.semaphore = semaphore;
signalInfo.value = ++semaphoreValue;
signalInfo.stageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT;
m_submission.signalInfos.push_back(signalInfo);
return submitToQueue(graphics.queueHandle, m_submission);
}
@ -184,9 +160,6 @@ namespace dxvk {
|| m_vkd->vkBeginCommandBuffer(m_sdmaBuffer, &info) != VK_SUCCESS)
Logger::err("DxvkCommandList: Failed to begin command buffer");
if (m_vkd->vkResetFences(m_vkd->device(), 1, &m_fence) != VK_SUCCESS)
Logger::err("DxvkCommandList: Failed to reset fence");
// Unconditionally mark the exec buffer as used. There
// is virtually no use case where this isn't correct.
m_cmdBuffersUsed = DxvkCmdBuffer::ExecBuffer;
@ -238,7 +211,6 @@ namespace dxvk {
VkResult DxvkCommandList::submitToQueue(
VkQueue queue,
VkFence fence,
const DxvkQueueSubmission& info) {
VkSubmitInfo2 submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO_2 };
submitInfo.waitSemaphoreInfoCount = info.waitInfos.size();
@ -248,7 +220,7 @@ namespace dxvk {
submitInfo.signalSemaphoreInfoCount = info.signalInfos.size();
submitInfo.pSignalSemaphoreInfos = info.signalInfos.data();
return m_vkd->vkQueueSubmit2(queue, 1, &submitInfo, fence);
return m_vkd->vkQueueSubmit2(queue, 1, &submitInfo, VK_NULL_HANDLE);
}
void DxvkCommandList::cmdBeginDebugUtilsLabel(VkDebugUtilsLabelEXT *pLabelInfo) {

View File

@ -72,19 +72,17 @@ namespace dxvk {
/**
* \brief Submits command list
*
* \param [in] queue Device queue
* \param [in] semaphore Global timeline semaphore
* \param [in,out] semaphoreValue Semaphore value. On input,
* this is the last signaled value of the semaphore so that
* synchronization can take place as needed. On ouput, this
* will contain the new value the semaphore gets signaled
* to by this submission.
* \returns Submission status
*/
VkResult submit();
/**
* \brief Synchronizes command buffer execution
*
* Waits for the fence associated with
* this command buffer to get signaled.
* \returns Synchronization status
*/
VkResult synchronize();
VkResult submit(
VkSemaphore semaphore,
uint64_t& semaphoreValue);
/**
* \brief Stat counters
@ -813,8 +811,6 @@ namespace dxvk {
Rc<vk::DeviceFn> m_vkd;
Rc<vk::InstanceFn> m_vki;
VkFence m_fence;
VkCommandPool m_graphicsPool = VK_NULL_HANDLE;
VkCommandPool m_transferPool = VK_NULL_HANDLE;
@ -822,8 +818,6 @@ namespace dxvk {
VkCommandBuffer m_initBuffer = VK_NULL_HANDLE;
VkCommandBuffer m_sdmaBuffer = VK_NULL_HANDLE;
VkSemaphore m_sdmaSemaphore = VK_NULL_HANDLE;
vk::PresenterSync m_wsiSemaphores = { };
DxvkCmdBufferFlags m_cmdBuffersUsed;
@ -853,7 +847,6 @@ namespace dxvk {
VkResult submitToQueue(
VkQueue queue,
VkFence fence,
const DxvkQueueSubmission& info);
};

View File

@ -7,20 +7,34 @@ namespace dxvk {
: m_device(device),
m_submitThread([this] () { submitCmdLists(); }),
m_finishThread([this] () { finishCmdLists(); }) {
auto vk = m_device->vkd();
VkSemaphoreTypeCreateInfo typeInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO };
typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE;
VkSemaphoreCreateInfo info = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, &typeInfo };
if (vk->vkCreateSemaphore(vk->device(), &info, nullptr, &m_semaphore))
throw DxvkError("Failed to create global timeline semaphore");
}
DxvkSubmissionQueue::~DxvkSubmissionQueue() {
auto vk = m_device->vkd();
{ std::unique_lock<dxvk::mutex> lock(m_mutex);
m_stopped.store(true);
}
m_appendCond.notify_all();
m_submitCond.notify_all();
m_submitThread.join();
m_finishThread.join();
synchronizeSemaphore(m_semaphoreValue);
vk->vkDestroySemaphore(vk->device(), m_semaphore, nullptr);
}
@ -81,6 +95,24 @@ namespace dxvk {
}
VkResult DxvkSubmissionQueue::synchronizeSemaphore(
uint64_t semaphoreValue) {
auto vk = m_device->vkd();
VkSemaphoreWaitInfo waitInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO };
waitInfo.semaphoreCount = 1;
waitInfo.pSemaphores = &m_semaphore;
waitInfo.pValues = &semaphoreValue;
VkResult vr = vk->vkWaitSemaphores(vk->device(), &waitInfo, ~0ull);
if (vr)
Logger::err(str::format("Failed to synchronize with global timeline semaphore: ", vr));
return vr;
}
void DxvkSubmissionQueue::submitCmdLists() {
env::setThreadName("dxvk-submit");
@ -104,7 +136,8 @@ namespace dxvk {
std::lock_guard<dxvk::mutex> lock(m_mutexQueue);
if (entry.submit.cmdList != nullptr) {
status = entry.submit.cmdList->submit();
status = entry.submit.cmdList->submit(m_semaphore, m_semaphoreValue);
entry.submit.semaphoreValue = m_semaphoreValue;
} else if (entry.present.presenter != nullptr) {
status = entry.present.presenter->presentImage();
}
@ -161,7 +194,7 @@ namespace dxvk {
VkResult status = m_lastError.load();
if (status != VK_ERROR_DEVICE_LOST)
status = entry.submit.cmdList->synchronize();
status = synchronizeSemaphore(entry.submit.semaphoreValue);
if (status != VK_SUCCESS) {
Logger::err(str::format("DxvkSubmissionQueue: Failed to sync fence: ", status));

View File

@ -33,6 +33,7 @@ namespace dxvk {
*/
struct DxvkSubmitInfo {
Rc<DxvkCommandList> cmdList;
uint64_t semaphoreValue;
};
@ -175,13 +176,16 @@ namespace dxvk {
private:
DxvkDevice* m_device;
DxvkDevice* m_device;
std::atomic<VkResult> m_lastError = { VK_SUCCESS };
std::atomic<VkResult> m_lastError = { VK_SUCCESS };
std::atomic<bool> m_stopped = { false };
std::atomic<uint32_t> m_pending = { 0u };
std::atomic<uint64_t> m_gpuIdle = { 0ull };
std::atomic<bool> m_stopped = { false };
std::atomic<uint32_t> m_pending = { 0u };
std::atomic<uint64_t> m_gpuIdle = { 0ull };
VkSemaphore m_semaphore = VK_NULL_HANDLE;
uint64_t m_semaphoreValue = 0ull;
dxvk::mutex m_mutex;
dxvk::mutex m_mutexQueue;
@ -196,8 +200,8 @@ namespace dxvk {
dxvk::thread m_submitThread;
dxvk::thread m_finishThread;
VkResult submitToQueue(
const DxvkSubmitInfo& submission);
VkResult synchronizeSemaphore(
uint64_t semaphoreValue);
void submitCmdLists();