1
0
mirror of https://github.com/EduApps-CDG/OpenDX synced 2024-12-30 09:45:37 +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( VkResult DxvkDevice::presentImage(
const Rc<vk::Presenter>& presenter, const Rc<vk::Presenter>& presenter,
VkSemaphore semaphore) { VkSemaphore semaphore) {
std::lock_guard<std::mutex> queueLock(m_submissionLock); DxvkPresentInfo presentInfo;
VkResult status = presenter->presentImage(semaphore); presentInfo.presenter = presenter;
presentInfo.waitSync = semaphore;
VkResult status = m_submissionQueue.present(presentInfo);
if (status != VK_SUCCESS) if (status != VK_SUCCESS)
return status; return status;
@ -278,32 +280,22 @@ namespace dxvk {
const Rc<DxvkCommandList>& commandList, const Rc<DxvkCommandList>& commandList,
VkSemaphore waitSync, VkSemaphore waitSync,
VkSemaphore wakeSync) { VkSemaphore wakeSync) {
VkResult status; DxvkSubmitInfo submitInfo;
submitInfo.cmdList = commandList;
{ // Queue submissions are not thread safe submitInfo.queue = m_graphicsQueue.queueHandle;
std::lock_guard<std::mutex> queueLock(m_submissionLock); submitInfo.waitSync = waitSync;
std::lock_guard<sync::Spinlock> statLock(m_statLock); submitInfo.wakeSync = wakeSync;
m_submissionQueue.submit(submitInfo);
m_statCounters.merge(commandList->statCounters());
m_statCounters.addCtr(DxvkStatCounter::QueueSubmitCount, 1); std::lock_guard<sync::Spinlock> statLock(m_statLock);
m_statCounters.merge(commandList->statCounters());
status = commandList->submit( m_statCounters.addCtr(DxvkStatCounter::QueueSubmitCount, 1);
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));
}
} }
void DxvkDevice::waitForIdle() { void DxvkDevice::waitForIdle() {
m_submissionQueue.synchronize();
if (m_vkd->vkDeviceWaitIdle(m_vkd->device()) != VK_SUCCESS) if (m_vkd->vkDeviceWaitIdle(m_vkd->device()) != VK_SUCCESS)
Logger::err("DxvkDevice: waitForIdle: Operation failed"); Logger::err("DxvkDevice: waitForIdle: Operation failed");
} }

View File

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

View File

@ -5,7 +5,8 @@ namespace dxvk {
DxvkSubmissionQueue::DxvkSubmissionQueue(DxvkDevice* device) DxvkSubmissionQueue::DxvkSubmissionQueue(DxvkDevice* device)
: m_device(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_stopped.store(true);
} }
m_condOnAdd.notify_one(); m_appendCond.notify_all();
m_thread.join(); m_submitCond.notify_all();
m_submitThread.join();
m_finishThread.join();
} }
void DxvkSubmissionQueue::submit(const Rc<DxvkCommandList>& cmdList) { void DxvkSubmissionQueue::submit(DxvkSubmitInfo submitInfo) {
{ std::unique_lock<std::mutex> lock(m_mutex); std::unique_lock<std::mutex> lock(m_mutex);
m_condOnTake.wait(lock, [this] { m_finishCond.wait(lock, [this] {
return m_entries.size() < MaxNumQueuedCommandBuffers; 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; if (m_stopped.load())
m_entries.push(cmdList); return;
m_condOnAdd.notify_one();
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"); env::setThreadName("dxvk-queue");
std::unique_lock<std::mutex> lock(m_mutex);
while (!m_stopped.load()) { 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); DxvkSubmitInfo submitInfo = std::move(m_finishQueue.front());
lock.unlock();
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();
}
if (cmdList != nullptr) { VkResult status = submitInfo.cmdList->synchronize();
VkResult status = cmdList->synchronize();
if (status == VK_SUCCESS) {
submitInfo.cmdList->signalEvents();
submitInfo.cmdList->reset();
if (status == VK_SUCCESS) { m_device->recycleCommandList(submitInfo.cmdList);
cmdList->signalEvents(); } else {
cmdList->reset(); Logger::err(str::format(
"DxvkSubmissionQueue: Failed to sync fence: ",
m_device->recycleCommandList(cmdList); status));
} else {
Logger::err(str::format(
"DxvkSubmissionQueue: Failed to sync fence: ",
status));
}
m_submits -= 1;
} }
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 "../util/thread.h"
#include "../vulkan/vulkan_presenter.h"
#include "dxvk_cmdlist.h" #include "dxvk_cmdlist.h"
namespace dxvk { namespace dxvk {
class DxvkDevice; 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 * \brief Submission queue
*/ */
class DxvkSubmissionQueue { class DxvkSubmissionQueue {
public: public:
DxvkSubmissionQueue(DxvkDevice* device); DxvkSubmissionQueue(DxvkDevice* device);
@ -30,35 +58,84 @@ namespace dxvk {
* \returns Pending submission count * \returns Pending submission count
*/ */
uint32_t pendingSubmissions() const { 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. * Queues a command list for submission on the
* This thread will wait for the command list * dedicated submission thread. Use this to take
* to finish executing on the GPU and signal * the submission overhead off the calling thread.
* any queries and events that are used by * \param [in] submitInfo Submission parameters
* the command list in question.
* \param [in] cmdList The command list
*/ */
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: private:
DxvkDevice* m_device; DxvkDevice* m_device;
std::atomic<bool> m_stopped = { false }; 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::mutex m_mutex;
std::condition_variable m_condOnAdd; std::mutex m_mutexQueue;
std::condition_variable m_condOnTake;
std::queue<Rc<DxvkCommandList>> m_entries;
dxvk::thread m_thread;
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();
}; };