Vulkan: Add a single background thread for pipeline creation

Add proper waits for compile-done

Don't rely on non-standard struct initialization of classes

Attempt to drain the pipeline compile queue before destroying the PipelineManager.

Vulkan: Bump the cache version for testing
This commit is contained in:
Henrik Rydgård 2019-06-16 21:57:22 +02:00
parent 62f4875e24
commit dda425b068
5 changed files with 122 additions and 9 deletions

View file

@ -1275,9 +1275,11 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
{ {
VKRGraphicsPipeline *pipeline = c.graphics_pipeline.pipeline; VKRGraphicsPipeline *pipeline = c.graphics_pipeline.pipeline;
if (!pipeline->pipeline) { if (!pipeline->pipeline) {
// Late! Compile it. // Stall processing, waiting for the compile queue to catch up.
if (!pipeline->Create(vulkan_)) std::unique_lock<std::mutex> lock(compileDoneMutex_);
break; while (!pipeline->pipeline) {
compileDone_.wait(lock);
}
} }
if (pipeline->pipeline != lastGraphicsPipeline) { if (pipeline->pipeline != lastGraphicsPipeline) {
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->pipeline); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->pipeline);
@ -1294,9 +1296,11 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
{ {
VKRComputePipeline *pipeline = c.compute_pipeline.pipeline; VKRComputePipeline *pipeline = c.compute_pipeline.pipeline;
if (!pipeline->pipeline) { if (!pipeline->pipeline) {
// Late! Compile it. // Stall processing, waiting for the compile queue to catch up.
if (!pipeline->Create(vulkan_)) std::unique_lock<std::mutex> lock(compileDoneMutex_);
break; while (!pipeline->pipeline) {
compileDone_.wait(lock);
}
} }
if (pipeline->pipeline != lastComputePipeline) { if (pipeline->pipeline != lastComputePipeline) {
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline->pipeline); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline->pipeline);

View file

@ -1,6 +1,9 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <mutex>
#include <condition_variable>
#include "Common/Data/Collections/Hashmaps.h" #include "Common/Data/Collections/Hashmaps.h"
#include "Common/GPU/Vulkan/VulkanContext.h" #include "Common/GPU/Vulkan/VulkanContext.h"
@ -254,6 +257,15 @@ public:
hacksEnabled_ = hacks; hacksEnabled_ = hacks;
} }
void NotifyCompileDone() {
compileDone_.notify_all();
}
void WaitForCompileNotification() {
std::unique_lock<std::mutex> lock(compileDoneMutex_);
compileDone_.wait(lock);
}
private: private:
void InitBackbufferRenderPass(); void InitBackbufferRenderPass();
@ -302,4 +314,8 @@ private:
// TODO: Enable based on compat.ini. // TODO: Enable based on compat.ini.
uint32_t hacksEnabled_ = 0; uint32_t hacksEnabled_ = 0;
// Compile done notifications.
std::mutex compileDoneMutex_;
std::condition_variable compileDone_;
}; };

View file

@ -27,7 +27,9 @@ bool VKRGraphicsPipeline::Create(VulkanContext *vulkan) {
// Already failed to create this one. // Already failed to create this one.
return false; return false;
} }
VkPipeline pipeline;
VkResult result = vkCreateGraphicsPipelines(vulkan->GetDevice(), desc->pipelineCache, 1, &desc->pipe, nullptr, &pipeline); VkResult result = vkCreateGraphicsPipelines(vulkan->GetDevice(), desc->pipelineCache, 1, &desc->pipe, nullptr, &pipeline);
this->pipeline = pipeline;
delete desc; delete desc;
desc = nullptr; desc = nullptr;
if (result == VK_INCOMPLETE) { if (result == VK_INCOMPLETE) {
@ -49,7 +51,9 @@ bool VKRComputePipeline::Create(VulkanContext *vulkan) {
// Already failed to create this one. // Already failed to create this one.
return false; return false;
} }
VkPipeline pipeline;
VkResult result = vkCreateComputePipelines(vulkan->GetDevice(), desc->pipelineCache, 1, &desc->pipe, nullptr, &pipeline); VkResult result = vkCreateComputePipelines(vulkan->GetDevice(), desc->pipelineCache, 1, &desc->pipe, nullptr, &pipeline);
this->pipeline = pipeline;
delete desc; delete desc;
desc = nullptr; desc = nullptr;
if (result != VK_SUCCESS) { if (result != VK_SUCCESS) {
@ -326,6 +330,8 @@ bool VulkanRenderManager::CreateBackbuffers() {
threadInitFrame_ = vulkan_->GetCurFrame(); threadInitFrame_ = vulkan_->GetCurFrame();
INFO_LOG(G3D, "Starting Vulkan submission thread (threadInitFrame_ = %d)", vulkan_->GetCurFrame()); INFO_LOG(G3D, "Starting Vulkan submission thread (threadInitFrame_ = %d)", vulkan_->GetCurFrame());
thread_ = std::thread(&VulkanRenderManager::ThreadFunc, this); thread_ = std::thread(&VulkanRenderManager::ThreadFunc, this);
INFO_LOG(G3D, "Starting Vulkan compiler thread");
compileThread_ = std::thread(&VulkanRenderManager::CompileThreadFunc, this);
} }
return true; return true;
} }
@ -349,6 +355,9 @@ void VulkanRenderManager::StopThread() {
} }
thread_.join(); thread_.join();
INFO_LOG(G3D, "Vulkan submission thread joined. Frame=%d", vulkan_->GetCurFrame()); INFO_LOG(G3D, "Vulkan submission thread joined. Frame=%d", vulkan_->GetCurFrame());
compileCond_.notify_all();
compileThread_.join();
INFO_LOG(G3D, "Vulkan compiler thread joined.");
// Eat whatever has been queued up for this frame if anything. // Eat whatever has been queued up for this frame if anything.
Wipe(); Wipe();
@ -415,6 +424,7 @@ VulkanRenderManager::~VulkanRenderManager() {
StopThread(); StopThread();
vulkan_->WaitUntilQueueIdle(); vulkan_->WaitUntilQueueIdle();
DrainCompileQueue();
VkDevice device = vulkan_->GetDevice(); VkDevice device = vulkan_->GetDevice();
vkDestroySemaphore(device, acquireSemaphore_, nullptr); vkDestroySemaphore(device, acquireSemaphore_, nullptr);
vkDestroySemaphore(device, renderingCompleteSemaphore_, nullptr); vkDestroySemaphore(device, renderingCompleteSemaphore_, nullptr);
@ -430,6 +440,42 @@ VulkanRenderManager::~VulkanRenderManager() {
queueRunner_.DestroyDeviceObjects(); queueRunner_.DestroyDeviceObjects();
} }
void VulkanRenderManager::CompileThreadFunc() {
SetCurrentThreadName("ShaderCompile");
while (true) {
std::vector<CompileQueueEntry> toCompile;
{
std::unique_lock<std::mutex> lock(compileMutex_);
if (compileQueue_.empty()) {
compileCond_.wait(lock);
}
toCompile = std::move(compileQueue_);
compileQueue_.clear();
}
if (!run_) {
break;
}
for (auto entry : toCompile) {
switch (entry.type) {
case CompileQueueEntry::Type::GRAPHICS:
entry.graphics->Create(vulkan_);
break;
case CompileQueueEntry::Type::COMPUTE:
entry.compute->Create(vulkan_);
break;
}
}
queueRunner_.NotifyCompileDone();
}
}
void VulkanRenderManager::DrainCompileQueue() {
std::unique_lock<std::mutex> lock(compileMutex_);
while (!compileQueue_.empty()) {
queueRunner_.WaitForCompileNotification();
}
}
void VulkanRenderManager::ThreadFunc() { void VulkanRenderManager::ThreadFunc() {
SetCurrentThreadName("RenderMan"); SetCurrentThreadName("RenderMan");
int threadFrame = threadInitFrame_; int threadFrame = threadInitFrame_;

View file

@ -10,6 +10,7 @@
#include <cstdint> #include <cstdint>
#include <mutex> #include <mutex>
#include <thread> #include <thread>
#include <queue>
#include "Common/System/Display.h" #include "Common/System/Display.h"
#include "Common/GPU/Vulkan/VulkanContext.h" #include "Common/GPU/Vulkan/VulkanContext.h"
@ -136,25 +137,45 @@ struct VKRComputePipelineDesc {
// Wrapped pipeline, which will later allow for background compilation while emulating the rest of the frame. // Wrapped pipeline, which will later allow for background compilation while emulating the rest of the frame.
struct VKRGraphicsPipeline { struct VKRGraphicsPipeline {
VKRGraphicsPipeline() {
pipeline = VK_NULL_HANDLE;
}
VKRGraphicsPipelineDesc *desc = nullptr; // While non-zero, is pending and pipeline isn't valid. VKRGraphicsPipelineDesc *desc = nullptr; // While non-zero, is pending and pipeline isn't valid.
VkPipeline pipeline = VK_NULL_HANDLE; std::atomic<VkPipeline> pipeline;
bool Create(VulkanContext *vulkan); bool Create(VulkanContext *vulkan);
}; };
struct VKRComputePipeline { struct VKRComputePipeline {
VKRComputePipeline() {
pipeline = VK_NULL_HANDLE;
}
VKRComputePipelineDesc *desc = nullptr; VKRComputePipelineDesc *desc = nullptr;
VkPipeline pipeline = VK_NULL_HANDLE; std::atomic<VkPipeline> pipeline;
bool Create(VulkanContext *vulkan); bool Create(VulkanContext *vulkan);
}; };
struct CompileQueueEntry {
CompileQueueEntry(VKRGraphicsPipeline *p) : type(Type::GRAPHICS), graphics(p) {}
CompileQueueEntry(VKRComputePipeline *p) : type(Type::COMPUTE), compute(p) {}
enum class Type {
GRAPHICS,
COMPUTE,
};
Type type;
VKRGraphicsPipeline *graphics = nullptr;
VKRComputePipeline *compute = nullptr;
};
class VulkanRenderManager { class VulkanRenderManager {
public: public:
VulkanRenderManager(VulkanContext *vulkan); VulkanRenderManager(VulkanContext *vulkan);
~VulkanRenderManager(); ~VulkanRenderManager();
void ThreadFunc(); void ThreadFunc();
void CompileThreadFunc();
void DrainCompileQueue();
// Makes sure that the GPU has caught up enough that we can start writing buffers of this frame again. // Makes sure that the GPU has caught up enough that we can start writing buffers of this frame again.
void BeginFrame(bool enableProfiling); void BeginFrame(bool enableProfiling);
@ -197,6 +218,20 @@ public:
VKRGraphicsPipeline *CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc) { VKRGraphicsPipeline *CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc) {
VKRGraphicsPipeline *pipeline = new VKRGraphicsPipeline(); VKRGraphicsPipeline *pipeline = new VKRGraphicsPipeline();
pipeline->desc = desc; pipeline->desc = desc;
compileMutex_.lock();
compileQueue_.push_back(CompileQueueEntry(pipeline));
compileCond_.notify_one();
compileMutex_.unlock();
return pipeline;
}
VKRComputePipeline *CreateComputePipeline(VKRComputePipelineDesc *desc) {
VKRComputePipeline *pipeline = new VKRComputePipeline();
pipeline->desc = desc;
compileMutex_.lock();
compileQueue_.push_back(CompileQueueEntry(pipeline));
compileCond_.notify_one();
compileMutex_.unlock();
return pipeline; return pipeline;
} }
@ -487,6 +522,14 @@ private:
int threadInitFrame_ = 0; int threadInitFrame_ = 0;
VulkanQueueRunner queueRunner_; VulkanQueueRunner queueRunner_;
// Shader compilation thread to compile while emulating the rest of the frame.
// Only one right now but we could use more.
std::thread compileThread_;
// Sync
std::condition_variable compileCond_;
std::mutex compileMutex_;
std::vector<CompileQueueEntry> compileQueue_;
// Swap chain management // Swap chain management
struct SwapchainImageData { struct SwapchainImageData {
VkImage image; VkImage image;

View file

@ -35,8 +35,12 @@ void PipelineManagerVulkan::Clear() {
pipelines_.Iterate([&](const VulkanPipelineKey &key, VulkanPipeline *value) { pipelines_.Iterate([&](const VulkanPipelineKey &key, VulkanPipeline *value) {
if (value->pipeline) { if (value->pipeline) {
vulkan_->Delete().QueueDeletePipeline(value->pipeline->pipeline); VkPipeline pipeline = value->pipeline->pipeline;
vulkan_->Delete().QueueDeletePipeline(pipeline);
delete value->pipeline; delete value->pipeline;
} else {
// Something went wrong.
ERROR_LOG(G3D, "Null pipeline found in PipelineManagerVulkan::Clear - didn't wait for asyncs?");
} }
delete value; delete value;
}); });