Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vulkan: Parallelize GLSL compilation #15589

Merged
merged 14 commits into from
Sep 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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