1
0
mirror of https://github.com/doitsujin/dxvk.git synced 2025-01-18 20:52:10 +01:00

[dxvk] Off-load command buffer submission to separate thread

Reduces load on the CS thread a bit, which may yield a small
performance improvement.
This commit is contained in:
Philip Rebohle 2019-05-05 14:08:07 +02:00
parent 4c0c66892a
commit b35f3c14df
No known key found for this signature in database
GPG Key ID: C8CC613427A31C99
4 changed files with 229 additions and 87 deletions

View File

@ -262,8 +262,10 @@ namespace dxvk {
VkResult DxvkDevice::presentImage(
const Rc<vk::Presenter>& presenter,
VkSemaphore semaphore) {
std::lock_guard<std::mutex> queueLock(m_submissionLock);
VkResult status = presenter->presentImage(semaphore);
DxvkPresentInfo presentInfo;
presentInfo.presenter = presenter;
presentInfo.waitSync = semaphore;
VkResult status = m_submissionQueue.present(presentInfo);
if (status != VK_SUCCESS)
return status;
@ -278,32 +280,22 @@ namespace dxvk {
const Rc<DxvkCommandList>& commandList,
VkSemaphore waitSync,
VkSemaphore wakeSync) {
VkResult status;
{ // Queue submissions are not thread safe
std::lock_guard<std::mutex> queueLock(m_submissionLock);
std::lock_guard<sync::Spinlock> statLock(m_statLock);
m_statCounters.merge(commandList->statCounters());
m_statCounters.addCtr(DxvkStatCounter::QueueSubmitCount, 1);
status = commandList->submit(
m_graphicsQueue.queueHandle,
waitSync, wakeSync);
}
if (status == VK_SUCCESS) {
// Add this to the set of running submissions
m_submissionQueue.submit(commandList);
} else {
Logger::err(str::format(
"DxvkDevice: Command buffer submission failed: ",
status));
}
DxvkSubmitInfo submitInfo;
submitInfo.cmdList = commandList;
submitInfo.queue = m_graphicsQueue.queueHandle;
submitInfo.waitSync = waitSync;
submitInfo.wakeSync = wakeSync;
m_submissionQueue.submit(submitInfo);
std::lock_guard<sync::Spinlock> statLock(m_statLock);
m_statCounters.merge(commandList->statCounters());
m_statCounters.addCtr(DxvkStatCounter::QueueSubmitCount, 1);
}
void DxvkDevice::waitForIdle() {
m_submissionQueue.synchronize();
if (m_vkd->vkDeviceWaitIdle(m_vkd->device()) != VK_SUCCESS)
Logger::err("DxvkDevice: waitForIdle: Operation failed");
}

View File

@ -339,6 +339,7 @@ namespace dxvk {
* presenter's \c presentImage method.
* \param [in] presenter The presenter
* \param [in] semaphore Sync semaphore
* \returns Status of the operation
*/
VkResult presentImage(
const Rc<vk::Presenter>& presenter,
@ -347,11 +348,11 @@ namespace dxvk {
/**
* \brief Submits a command list
*
* Synchronization arguments are optional.
* Submits the given command list to the device using
* the given set of optional synchronization primitives.
* \param [in] commandList The command list to submit
* \param [in] waitSync (Optional) Semaphore to wait on
* \param [in] wakeSync (Optional) Semaphore to notify
* \returns Synchronization fence
*/
void submitCommandList(
const Rc<DxvkCommandList>& commandList,
@ -366,7 +367,8 @@ namespace dxvk {
* to lock the queue before submitting command buffers.
*/
void lockSubmission() {
m_submissionLock.lock();
m_submissionQueue.synchronize();
m_submissionQueue.lockDeviceQueue();
}
/**
@ -376,7 +378,7 @@ namespace dxvk {
* itself can use them for submissions again.
*/
void unlockSubmission() {
m_submissionLock.unlock();
m_submissionQueue.unlockDeviceQueue();
}
/**
@ -430,7 +432,6 @@ namespace dxvk {
sync::Spinlock m_statLock;
DxvkStatCounters m_statCounters;
std::mutex m_submissionLock;
DxvkDeviceQueue m_graphicsQueue;
DxvkDeviceQueue m_presentQueue;

View File

@ -5,7 +5,8 @@ namespace dxvk {
DxvkSubmissionQueue::DxvkSubmissionQueue(DxvkDevice* device)
: m_device(device),
m_thread([this] () { threadFunc(); }) {
m_submitThread([this] () { submitCmdLists(); }),
m_finishThread([this] () { finishCmdLists(); }) {
}
@ -15,61 +16,132 @@ namespace dxvk {
m_stopped.store(true);
}
m_condOnAdd.notify_one();
m_thread.join();
m_appendCond.notify_all();
m_submitCond.notify_all();
m_submitThread.join();
m_finishThread.join();
}
void DxvkSubmissionQueue::submit(const Rc<DxvkCommandList>& cmdList) {
{ std::unique_lock<std::mutex> lock(m_mutex);
m_condOnTake.wait(lock, [this] {
return m_entries.size() < MaxNumQueuedCommandBuffers;
void DxvkSubmissionQueue::submit(DxvkSubmitInfo submitInfo) {
std::unique_lock<std::mutex> lock(m_mutex);
m_finishCond.wait(lock, [this] {
return m_submitQueue.size() + m_finishQueue.size() <= MaxNumQueuedCommandBuffers;
});
m_pending += 1;
m_submitQueue.push(std::move(submitInfo));
m_appendCond.notify_all();
}
VkResult DxvkSubmissionQueue::present(DxvkPresentInfo presentInfo) {
this->synchronize();
std::unique_lock<std::mutex> lock(m_mutexQueue);
return presentInfo.presenter->presentImage(presentInfo.waitSync);
}
void DxvkSubmissionQueue::synchronize() {
std::unique_lock<std::mutex> lock(m_mutex);
m_submitCond.wait(lock, [this] {
return m_submitQueue.empty();
});
}
void DxvkSubmissionQueue::lockDeviceQueue() {
m_mutexQueue.lock();
}
void DxvkSubmissionQueue::unlockDeviceQueue() {
m_mutexQueue.unlock();
}
void DxvkSubmissionQueue::submitCmdLists() {
env::setThreadName("dxvk-submit");
std::unique_lock<std::mutex> lock(m_mutex);
while (!m_stopped.load()) {
m_appendCond.wait(lock, [this] {
return m_stopped.load() || !m_submitQueue.empty();
});
m_submits += 1;
m_entries.push(cmdList);
m_condOnAdd.notify_one();
if (m_stopped.load())
return;
DxvkSubmitInfo submitInfo = std::move(m_submitQueue.front());
lock.unlock();
// Submit command buffer to device
VkResult status;
{ std::lock_guard<std::mutex> lock(m_mutexQueue);
status = submitInfo.cmdList->submit(
submitInfo.queue,
submitInfo.waitSync,
submitInfo.wakeSync);
}
// On success, pass it on to the queue thread
lock = std::unique_lock<std::mutex>(m_mutex);
if (status == VK_SUCCESS) {
m_finishQueue.push(std::move(submitInfo));
m_submitQueue.pop();
m_submitCond.notify_all();
} else {
Logger::err(str::format(
"DxvkSubmissionQueue: Command submission failed with ",
status));
m_pending -= 1;
}
}
}
void DxvkSubmissionQueue::threadFunc() {
void DxvkSubmissionQueue::finishCmdLists() {
env::setThreadName("dxvk-queue");
std::unique_lock<std::mutex> lock(m_mutex);
while (!m_stopped.load()) {
Rc<DxvkCommandList> cmdList;
m_submitCond.wait(lock, [this] {
return m_stopped.load() || !m_finishQueue.empty();
});
if (m_stopped.load())
return;
{ std::unique_lock<std::mutex> lock(m_mutex);
m_condOnAdd.wait(lock, [this] {
return m_stopped.load() || (m_entries.size() != 0);
});
if (m_entries.size() != 0) {
cmdList = std::move(m_entries.front());
m_entries.pop();
}
m_condOnTake.notify_one();
}
DxvkSubmitInfo submitInfo = std::move(m_finishQueue.front());
lock.unlock();
if (cmdList != nullptr) {
VkResult status = cmdList->synchronize();
VkResult status = submitInfo.cmdList->synchronize();
if (status == VK_SUCCESS) {
submitInfo.cmdList->signalEvents();
submitInfo.cmdList->reset();
if (status == VK_SUCCESS) {
cmdList->signalEvents();
cmdList->reset();
m_device->recycleCommandList(cmdList);
} else {
Logger::err(str::format(
"DxvkSubmissionQueue: Failed to sync fence: ",
status));
}
m_submits -= 1;
m_device->recycleCommandList(submitInfo.cmdList);
} else {
Logger::err(str::format(
"DxvkSubmissionQueue: Failed to sync fence: ",
status));
}
lock = std::unique_lock<std::mutex>(m_mutex);
m_pending -= 1;
m_finishQueue.pop();
m_finishCond.notify_all();
}
}

View File

@ -6,17 +6,45 @@
#include "../util/thread.h"
#include "../vulkan/vulkan_presenter.h"
#include "dxvk_cmdlist.h"
namespace dxvk {
class DxvkDevice;
/**
* \brief Queue submission info
*
* Stores parameters used to submit
* a command buffer to the device.
*/
struct DxvkSubmitInfo {
Rc<DxvkCommandList> cmdList;
VkQueue queue;
VkSemaphore waitSync;
VkSemaphore wakeSync;
};
/**
* \brief Present info
*
* Stores parameters used to present
* a swap chain image on the device.
*/
struct DxvkPresentInfo {
Rc<vk::Presenter> presenter;
VkSemaphore waitSync;
};
/**
* \brief Submission queue
*/
class DxvkSubmissionQueue {
public:
DxvkSubmissionQueue(DxvkDevice* device);
@ -30,35 +58,84 @@ namespace dxvk {
* \returns Pending submission count
*/
uint32_t pendingSubmissions() const {
return m_submits.load();
return m_pending.load();
}
/**
* \brief Submits a command list
* \brief Submits a command list asynchronously
*
* Submits a command list to the queue thread.
* This thread will wait for the command list
* to finish executing on the GPU and signal
* any queries and events that are used by
* the command list in question.
* \param [in] cmdList The command list
* Queues a command list for submission on the
* dedicated submission thread. Use this to take
* the submission overhead off the calling thread.
* \param [in] submitInfo Submission parameters
*/
void submit(const Rc<DxvkCommandList>& cmdList);
void submit(
DxvkSubmitInfo submitInfo);
/**
* \brief Presents an image synchronously
*
* Waits for queued command lists to be submitted
* and then presents the current swap chain image
* of the presenter. May stall the calling thread.
* \param [in] present Present parameters
* \returns Status of the operation
*/
VkResult present(
DxvkPresentInfo present);
/**
* \brief Synchronizes with queue submissions
*
* Waits for all pending command lists to be
* submitted to the GPU before returning.
*/
void synchronize();
/**
* \brief Locks device queue
*
* Locks the mutex that protects the Vulkan queue
* that DXVK uses for command buffer submission.
* This is needed when the app submits its own
* command buffers to the queue.
*/
void lockDeviceQueue();
/**
* \brief Unlocks device queue
*
* Unlocks the mutex that protects the Vulkan
* queue used for command buffer submission.
*/
void unlockDeviceQueue();
private:
DxvkDevice* m_device;
std::atomic<bool> m_stopped = { false };
std::atomic<uint32_t> m_submits = { 0u };
std::atomic<uint32_t> m_pending = { 0u };
std::mutex m_mutex;
std::condition_variable m_condOnAdd;
std::condition_variable m_condOnTake;
std::queue<Rc<DxvkCommandList>> m_entries;
dxvk::thread m_thread;
std::mutex m_mutexQueue;
void threadFunc();
std::condition_variable m_appendCond;
std::condition_variable m_submitCond;
std::condition_variable m_finishCond;
std::queue<DxvkSubmitInfo> m_submitQueue;
std::queue<DxvkSubmitInfo> m_finishQueue;
dxvk::thread m_submitThread;
dxvk::thread m_finishThread;
VkResult submitToQueue(
const DxvkSubmitInfo& submission);
void submitCmdLists();
void finishCmdLists();
};