Skip to content

Commit

Permalink
Merge pull request #15589 from hrydgard/glsl-compilation-parallelization
Browse files Browse the repository at this point in the history
Vulkan: Parallelize GLSL compilation
  • Loading branch information
hrydgard authored Sep 3, 2022
2 parents 99681ff + fb3f417 commit b92ea74
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 121 deletions.
31 changes: 8 additions & 23 deletions Common/GPU/Vulkan/VulkanQueueRunner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1182,18 +1182,11 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c

case VKRRenderCommand::BIND_GRAPHICS_PIPELINE:
{
VKRGraphicsPipeline *pipeline = c.graphics_pipeline.pipeline;
if (pipeline->Pending()) {
// Stall processing, waiting for the compile queue to catch up.
std::unique_lock<std::mutex> lock(compileDoneMutex_);
while (!pipeline->pipeline) {
compileDone_.wait(lock);
}
}
if (pipeline->pipeline != lastGraphicsPipeline && pipeline->pipeline != VK_NULL_HANDLE) {
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->pipeline);
VkPipeline pipeline = c.graphics_pipeline.pipeline->BlockUntilReady();
if (pipeline != lastGraphicsPipeline && pipeline != VK_NULL_HANDLE) {
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
pipelineLayout = c.pipeline.pipelineLayout;
lastGraphicsPipeline = pipeline->pipeline;
lastGraphicsPipeline = pipeline;
// Reset dynamic state so it gets refreshed with the new pipeline.
lastStencilWriteMask = -1;
lastStencilCompareMask = -1;
Expand All @@ -1204,18 +1197,11 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c

case VKRRenderCommand::BIND_COMPUTE_PIPELINE:
{
VKRComputePipeline *pipeline = c.compute_pipeline.pipeline;
if (pipeline->Pending()) {
// Stall processing, waiting for the compile queue to catch up.
std::unique_lock<std::mutex> lock(compileDoneMutex_);
while (!pipeline->pipeline) {
compileDone_.wait(lock);
}
}
if (pipeline->pipeline != lastComputePipeline && pipeline->pipeline != VK_NULL_HANDLE) {
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline->pipeline);
VkPipeline pipeline = c.graphics_pipeline.pipeline->BlockUntilReady();
if (pipeline != lastComputePipeline && pipeline != VK_NULL_HANDLE) {
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
pipelineLayout = c.pipeline.pipelineLayout;
lastComputePipeline = pipeline->pipeline;
lastComputePipeline = pipeline;
}
break;
}
Expand Down Expand Up @@ -1335,7 +1321,6 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
}
default:
ERROR_LOG(G3D, "Unimpl queue command");
;
}
}
vkCmdEndRenderPass(cmd);
Expand Down
6 changes: 3 additions & 3 deletions Common/GPU/Vulkan/VulkanQueueRunner.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include <mutex>
#include <condition_variable>


#include "Common/Thread/Promise.h"
#include "Common/Data/Collections/Hashmaps.h"
#include "Common/GPU/Vulkan/VulkanContext.h"
#include "Common/GPU/Vulkan/VulkanBarrier.h"
Expand Down Expand Up @@ -55,11 +55,11 @@ struct VkRenderData {
VkPipelineLayout pipelineLayout;
} pipeline;
struct {
VKRGraphicsPipeline *pipeline;
Promise<VkPipeline> *pipeline;
VkPipelineLayout pipelineLayout;
} graphics_pipeline;
struct {
VKRComputePipeline *pipeline;
Promise<VkPipeline> *pipeline;
VkPipelineLayout pipelineLayout;
} compute_pipeline;
struct {
Expand Down
48 changes: 43 additions & 5 deletions Common/GPU/Vulkan/VulkanRenderManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "Common/Log.h"
#include "Common/StringUtils.h"
#include "Common/TimeUtil.h"

#include "Common/GPU/Vulkan/VulkanAlloc.h"
#include "Common/GPU/Vulkan/VulkanContext.h"
Expand All @@ -29,23 +30,54 @@ bool VKRGraphicsPipeline::Create(VulkanContext *vulkan) {
// Already failed to create this one.
return false;
}

// Fill in the last part of the desc since now it's time to block.
VkShaderModule vs = desc->vertexShader->BlockUntilReady();
VkShaderModule fs = desc->fragmentShader->BlockUntilReady();

if (!vs || !fs) {
ERROR_LOG(G3D, "Failed creating graphics pipeline - missing shader modules");
// We're kinda screwed here?
return false;
}

VkPipelineShaderStageCreateInfo ss[2]{};
ss[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
ss[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
ss[0].pSpecializationInfo = nullptr;
ss[0].module = vs;
ss[0].pName = "main";
ss[0].flags = 0;
ss[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
ss[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
ss[1].pSpecializationInfo = nullptr;
ss[1].module = fs;
ss[1].pName = "main";
ss[1].flags = 0;

desc->pipe.pStages = ss;
desc->pipe.stageCount = 2;

double start = time_now_d();
VkPipeline vkpipeline;
VkResult result = vkCreateGraphicsPipelines(vulkan->GetDevice(), desc->pipelineCache, 1, &desc->pipe, nullptr, &vkpipeline);

INFO_LOG(G3D, "Pipeline creation time: %0.2f ms", (time_now_d() - start) * 1000.0);

bool success = true;
if (result == VK_INCOMPLETE) {
// Bad (disallowed by spec) return value seen on Adreno in Burnout :( Try to ignore?
// Would really like to log more here, we could probably attach more info to desc.
//
// At least create a null placeholder to avoid creating over and over if something is broken.
pipeline = VK_NULL_HANDLE;
pipeline->Post(VK_NULL_HANDLE);
success = false;
} else if (result != VK_SUCCESS) {
pipeline = VK_NULL_HANDLE;
pipeline->Post(VK_NULL_HANDLE);
ERROR_LOG(G3D, "Failed creating graphics pipeline! result='%s'", VulkanResultToString(result));
success = false;
} else {
pipeline = vkpipeline;
pipeline->Post(vkpipeline);
}

delete desc;
Expand All @@ -63,11 +95,11 @@ bool VKRComputePipeline::Create(VulkanContext *vulkan) {

bool success = true;
if (result != VK_SUCCESS) {
pipeline = VK_NULL_HANDLE;
pipeline->Post(VK_NULL_HANDLE);
ERROR_LOG(G3D, "Failed creating compute pipeline! result='%s'", VulkanResultToString(result));
success = false;
} else {
pipeline = vkpipeline;
pipeline->Post(vkpipeline);
}

delete desc;
Expand Down Expand Up @@ -449,6 +481,12 @@ void VulkanRenderManager::CompileThreadFunc() {
if (!run_) {
break;
}

INFO_LOG(G3D, "Compilation thread has %d pipelines to create", (int)toCompile.size());

// TODO: Here we can sort the pending pipelines by vertex and fragment shaders,
// and split up further.
// Those with the same pairs of shaders should be on the same thread.
for (auto &entry : toCompile) {
switch (entry.type) {
case CompileQueueEntry::Type::GRAPHICS:
Expand Down
19 changes: 13 additions & 6 deletions Common/GPU/Vulkan/VulkanRenderManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <thread>
#include <queue>

#include "Common/Thread/Promise.h"
#include "Common/System/Display.h"
#include "Common/GPU/Vulkan/VulkanContext.h"
#include "Common/Data/Convert/SmallDataConvert.h"
Expand Down Expand Up @@ -121,7 +122,11 @@ struct VKRGraphicsPipelineDesc {
VkPipelineDynamicStateCreateInfo ds{ VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO };
VkPipelineRasterizationStateCreateInfo rs{ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO };
VkPipelineMultisampleStateCreateInfo ms{ VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO };
VkPipelineShaderStageCreateInfo shaderStageInfo[2]{};

// Replaced the ShaderStageInfo with promises here so we can wait for compiles to finish.
Promise<VkShaderModule> *vertexShader;
Promise<VkShaderModule> *fragmentShader;

VkPipelineInputAssemblyStateCreateInfo inputAssembly{ VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO };
VkVertexInputAttributeDescription attrs[8]{};
VkVertexInputBindingDescription ibd{};
Expand All @@ -139,10 +144,12 @@ struct VKRComputePipelineDesc {
// Wrapped pipeline, which will later allow for background compilation while emulating the rest of the frame.
struct VKRGraphicsPipeline {
VKRGraphicsPipeline() {
pipeline = VK_NULL_HANDLE;
pipeline = Promise<VkPipeline>::CreateEmpty();
}

VKRGraphicsPipelineDesc *desc = nullptr; // While non-zero, is pending and pipeline isn't valid.
std::atomic<VkPipeline> pipeline;

Promise<VkPipeline> *pipeline;

bool Create(VulkanContext *vulkan);
bool Pending() const {
Expand All @@ -155,7 +162,7 @@ struct VKRComputePipeline {
pipeline = VK_NULL_HANDLE;
}
VKRComputePipelineDesc *desc = nullptr;
std::atomic<VkPipeline> pipeline;
Promise<VkPipeline> *pipeline;

bool Create(VulkanContext *vulkan);
bool Pending() const {
Expand Down Expand Up @@ -256,7 +263,7 @@ class VulkanRenderManager {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
_dbg_assert_(pipeline != nullptr);
VkRenderData data{ VKRRenderCommand::BIND_GRAPHICS_PIPELINE };
data.graphics_pipeline.pipeline = pipeline;
data.graphics_pipeline.pipeline = pipeline->pipeline;
data.graphics_pipeline.pipelineLayout = pipelineLayout;
curPipelineFlags_ |= flags;
curRenderStep_->commands.push_back(data);
Expand All @@ -266,7 +273,7 @@ class VulkanRenderManager {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
_dbg_assert_(pipeline != nullptr);
VkRenderData data{ VKRRenderCommand::BIND_COMPUTE_PIPELINE };
data.compute_pipeline.pipeline = pipeline;
data.compute_pipeline.pipeline = pipeline->pipeline;
data.compute_pipeline.pipelineLayout = pipelineLayout;
curPipelineFlags_ |= flags;
curRenderStep_->commands.push_back(data);
Expand Down
4 changes: 2 additions & 2 deletions Common/Thread/Channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct Mailbox {

std::mutex mutex_;
std::condition_variable condvar_;
T data_ = nullptr;
T data_{};

T Wait() {
std::unique_lock<std::mutex> lock(mutex_);
Expand All @@ -43,7 +43,7 @@ struct Mailbox {
std::unique_lock<std::mutex> lock(mutex_);
if (!data_) {
data_ = data;
condvar_.notify_one();
condvar_.notify_all();
return true;
} else {
// Already has value.
Expand Down
38 changes: 35 additions & 3 deletions Common/Thread/Promise.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#pragma once

#include <functional>
#include <mutex>

#include "Common/Log.h"
#include "Common/Thread/Channel.h"
#include "Common/Thread/ThreadManager.h"

Expand Down Expand Up @@ -33,6 +35,7 @@ class PromiseTask : public Task {
// Has ownership over the data. Single use.
// TODO: Split Mailbox (rx_ and tx_) up into separate proxy objects.
// NOTE: Poll/BlockUntilReady should only be used from one thread.
// TODO: Make movable?
template<class T>
class Promise {
public:
Expand All @@ -47,15 +50,36 @@ class Promise {
return promise;
}

static Promise<T> *AlreadyDone(T data) {
Promise<T> *promise = new Promise<T>();
promise->data_ = data;
promise->ready_ = true;
return promise;
}

static Promise<T> *CreateEmpty() {
Mailbox<T> *mailbox = new Mailbox<T>();
Promise<T> *promise = new Promise<T>();
promise->rx_ = mailbox;
return promise;
}

// Allow an empty promise to spawn, too, in case we want to delay it.
void SpawnEmpty(ThreadManager *threadman, std::function<T()> fun, TaskType taskType) {
PromiseTask<T> *task = new PromiseTask<T>(fun, rx_, taskType);
threadman->EnqueueTask(task);
}

~Promise() {
std::lock_guard<std::mutex> guard(readyMutex_);
// A promise should have been fulfilled before it's destroyed.
_assert_(ready_);
_assert_(!rx_);
delete data_;
}

// Returns T if the data is ready, nullptr if it's not.
T Poll() {
std::lock_guard<std::mutex> guard(readyMutex_);
if (ready_) {
return data_;
} else {
Expand All @@ -71,6 +95,7 @@ class Promise {
}

T BlockUntilReady() {
std::lock_guard<std::mutex> guard(readyMutex_);
if (ready_) {
return data_;
} else {
Expand All @@ -82,10 +107,17 @@ class Promise {
}
}

// For outside injection of data, when not using Spawn
void Post(T data) {
rx_->Send(data);
}

private:
Promise() {}

T data_ = nullptr;
// Promise can only be constructed in Spawn (or AlreadyDone).
T data_{};
bool ready_ = false;
Mailbox<T> *rx_;
std::mutex readyMutex_;
Mailbox<T> *rx_ = nullptr;
};
31 changes: 5 additions & 26 deletions GPU/Vulkan/PipelineManagerVulkan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ void PipelineManagerVulkan::Clear() {

pipelines_.Iterate([&](const VulkanPipelineKey &key, VulkanPipeline *value) {
if (value->pipeline) {
VkPipeline pipeline = value->pipeline->pipeline;
VkPipeline pipeline = value->pipeline->pipeline->BlockUntilReady();
vulkan_->Delete().QueueDeletePipeline(pipeline);
vulkan_->Delete().QueueCallback([](void *p) {
VKRGraphicsPipeline *pipeline = (VKRGraphicsPipeline *)p;
Expand Down Expand Up @@ -256,28 +256,8 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager,
ms.pSampleMask = nullptr;
ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;

VkPipelineShaderStageCreateInfo *ss = &desc->shaderStageInfo[0];
ss[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
ss[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
ss[0].pSpecializationInfo = nullptr;
ss[0].module = vs->GetModule();
ss[0].pName = "main";
ss[0].flags = 0;
ss[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
ss[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
ss[1].pSpecializationInfo = nullptr;
ss[1].module = fs->GetModule();
ss[1].pName = "main";
ss[1].flags = 0;

if (!ss[0].module || !ss[1].module) {
ERROR_LOG(G3D, "Failed creating graphics pipeline - bad shaders");
// Create a placeholder to avoid creating over and over if shader compiler broken.
VulkanPipeline *nullPipeline = new VulkanPipeline();
nullPipeline->pipeline = VK_NULL_HANDLE;
nullPipeline->flags = 0;
return nullPipeline;
}
desc->fragmentShader = fs->GetModule();
desc->vertexShader = vs->GetModule();

VkPipelineInputAssemblyStateCreateInfo &inputAssembly = desc->inputAssembly;
inputAssembly.flags = 0;
Expand Down Expand Up @@ -321,7 +301,6 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager,
VkGraphicsPipelineCreateInfo &pipe = desc->pipe;
pipe.flags = 0;
pipe.stageCount = 2;
pipe.pStages = ss;
pipe.basePipelineIndex = 0;

pipe.pColorBlendState = &desc->cbs;
Expand Down Expand Up @@ -633,8 +612,8 @@ void PipelineManagerVulkan::SaveCache(FILE *file, bool saveRawPipelineCache, Sha
pipelines_.Iterate([&](const VulkanPipelineKey &pkey, VulkanPipeline *value) {
if (failed)
return;
VulkanVertexShader *vshader = shaderManager->GetVertexShaderFromModule(pkey.vShader);
VulkanFragmentShader *fshader = shaderManager->GetFragmentShaderFromModule(pkey.fShader);
VulkanVertexShader *vshader = shaderManager->GetVertexShaderFromModule(pkey.vShader->BlockUntilReady());
VulkanFragmentShader *fshader = shaderManager->GetFragmentShaderFromModule(pkey.fShader->BlockUntilReady());
if (!vshader || !fshader) {
failed = true;
return;
Expand Down
Loading

0 comments on commit b92ea74

Please sign in to comment.