[Vulkan] Add thread pool for async shader compilation
Uses priority queue with higher priority on shaders with visible render targets
This commit is contained in:
@@ -2446,17 +2446,27 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type,
|
|||||||
// Create the pipeline (for this, need the render pass from the render target
|
// Create the pipeline (for this, need the render pass from the render target
|
||||||
// cache), translating the shaders - doing this now to obtain the used
|
// cache), translating the shaders - doing this now to obtain the used
|
||||||
// textures.
|
// textures.
|
||||||
VkPipeline pipeline;
|
VulkanPipelineCache::Pipeline* pipeline;
|
||||||
const VulkanPipelineCache::PipelineLayoutProvider* pipeline_layout_provider;
|
|
||||||
if (!pipeline_cache_->ConfigurePipeline(
|
if (!pipeline_cache_->ConfigurePipeline(
|
||||||
vertex_shader_translation, pixel_shader_translation,
|
vertex_shader_translation, pixel_shader_translation,
|
||||||
primitive_processing_result, normalized_depth_control,
|
primitive_processing_result, normalized_depth_control,
|
||||||
normalized_color_mask,
|
normalized_color_mask,
|
||||||
render_target_cache_->last_update_render_pass_key(), pipeline,
|
render_target_cache_->last_update_render_pass_key(), &pipeline)) {
|
||||||
pipeline_layout_provider)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VkPipeline current_pipeline =
|
||||||
|
pipeline->pipeline.load(std::memory_order_acquire);
|
||||||
|
if (current_pipeline == VK_NULL_HANDLE) {
|
||||||
|
// Pipeline is not ready yet - wait for it to be created.
|
||||||
|
pipeline_cache_->EndSubmission();
|
||||||
|
current_pipeline = pipeline->pipeline.load(std::memory_order_acquire);
|
||||||
|
if (current_pipeline == VK_NULL_HANDLE) {
|
||||||
|
// Still not ready - something is wrong.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update the textures before most other work in the submission because
|
// Update the textures before most other work in the submission because
|
||||||
// samplers depend on this (and in case of sampler overflow in a submission,
|
// samplers depend on this (and in case of sampler overflow in a submission,
|
||||||
// submissions must be split) - may perform dispatches and copying.
|
// submissions must be split) - may perform dispatches and copying.
|
||||||
@@ -2470,14 +2480,17 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type,
|
|||||||
// Update the graphics pipeline, and if the new graphics pipeline has a
|
// Update the graphics pipeline, and if the new graphics pipeline has a
|
||||||
// different layout, invalidate incompatible descriptor sets before updating
|
// different layout, invalidate incompatible descriptor sets before updating
|
||||||
// current_guest_graphics_pipeline_layout_.
|
// current_guest_graphics_pipeline_layout_.
|
||||||
if (current_guest_graphics_pipeline_ != pipeline) {
|
// The pipeline may be not ready yet if created asynchronously.
|
||||||
|
// EndSubmission must be called before submitting the command buffer to
|
||||||
|
// await its creation.
|
||||||
|
if (current_guest_graphics_pipeline_ != current_pipeline) {
|
||||||
deferred_command_buffer_.CmdVkBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS,
|
deferred_command_buffer_.CmdVkBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||||
pipeline);
|
current_pipeline);
|
||||||
current_guest_graphics_pipeline_ = pipeline;
|
current_guest_graphics_pipeline_ = current_pipeline;
|
||||||
current_external_graphics_pipeline_ = VK_NULL_HANDLE;
|
current_external_graphics_pipeline_ = VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
auto pipeline_layout =
|
auto pipeline_layout =
|
||||||
static_cast<const PipelineLayout*>(pipeline_layout_provider);
|
static_cast<const PipelineLayout*>(pipeline->pipeline_layout);
|
||||||
if (current_guest_graphics_pipeline_layout_ != pipeline_layout) {
|
if (current_guest_graphics_pipeline_layout_ != pipeline_layout) {
|
||||||
if (current_guest_graphics_pipeline_layout_) {
|
if (current_guest_graphics_pipeline_layout_) {
|
||||||
// Keep descriptor set layouts for which the new pipeline layout is
|
// Keep descriptor set layouts for which the new pipeline layout is
|
||||||
|
|||||||
@@ -45,6 +45,14 @@ namespace shaders {
|
|||||||
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/tessellation_adaptive_vs.h"
|
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/tessellation_adaptive_vs.h"
|
||||||
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/tessellation_indexed_vs.h"
|
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/tessellation_indexed_vs.h"
|
||||||
} // namespace shaders
|
} // namespace shaders
|
||||||
|
|
||||||
|
DEFINE_int32(
|
||||||
|
vulkan_pipeline_creation_threads, -1,
|
||||||
|
"Number of threads used for graphics pipeline creation. -1 to calculate "
|
||||||
|
"automatically (75% of logical CPU cores), a positive number to specify "
|
||||||
|
"the number of threads explicitly (up to the number of logical CPU cores), "
|
||||||
|
"0 to disable multithreaded pipeline creation.",
|
||||||
|
"Vulkan");
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
namespace vulkan {
|
namespace vulkan {
|
||||||
@@ -167,10 +175,52 @@ bool VulkanPipelineCache::Initialize() {
|
|||||||
vk_pipeline_cache_ = VK_NULL_HANDLE;
|
vk_pipeline_cache_ = VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t logical_processor_count = xe::threading::logical_processor_count();
|
||||||
|
if (!logical_processor_count) {
|
||||||
|
// Pick some reasonable amount if couldn't determine the number of cores.
|
||||||
|
logical_processor_count = 6;
|
||||||
|
}
|
||||||
|
creation_completion_event_ =
|
||||||
|
xe::threading::Event::CreateManualResetEvent(true);
|
||||||
|
assert_not_null(creation_completion_event_);
|
||||||
|
if (cvars::vulkan_pipeline_creation_threads != 0) {
|
||||||
|
size_t creation_thread_count;
|
||||||
|
if (cvars::vulkan_pipeline_creation_threads < 0) {
|
||||||
|
creation_thread_count =
|
||||||
|
std::max(logical_processor_count * 3 / 4, uint32_t(1));
|
||||||
|
} else {
|
||||||
|
creation_thread_count =
|
||||||
|
std::min(uint32_t(cvars::vulkan_pipeline_creation_threads),
|
||||||
|
logical_processor_count);
|
||||||
|
}
|
||||||
|
creation_threads_shutdown_ = false;
|
||||||
|
for (size_t i = 0; i < creation_thread_count; ++i) {
|
||||||
|
std::unique_ptr<xe::threading::Thread> creation_thread =
|
||||||
|
xe::threading::Thread::Create({}, [this]() { CreationThread(); });
|
||||||
|
assert_not_null(creation_thread);
|
||||||
|
creation_thread->set_name("Vulkan Pipelines");
|
||||||
|
creation_threads_.push_back(std::move(creation_thread));
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanPipelineCache::Shutdown() {
|
void VulkanPipelineCache::Shutdown() {
|
||||||
|
// Shut down all threads, before destroying the pipelines since they may be
|
||||||
|
// creating them.
|
||||||
|
if (!creation_threads_.empty()) {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(creation_request_lock_);
|
||||||
|
creation_threads_shutdown_ = true;
|
||||||
|
}
|
||||||
|
creation_request_cond_.notify_all();
|
||||||
|
for (size_t i = 0; i < creation_threads_.size(); ++i) {
|
||||||
|
xe::threading::Wait(creation_threads_[i].get(), false);
|
||||||
|
}
|
||||||
|
creation_threads_.clear();
|
||||||
|
}
|
||||||
|
creation_completion_event_.reset();
|
||||||
|
|
||||||
const ui::vulkan::VulkanDevice* const vulkan_device =
|
const ui::vulkan::VulkanDevice* const vulkan_device =
|
||||||
command_processor_.GetVulkanDevice();
|
command_processor_.GetVulkanDevice();
|
||||||
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
||||||
@@ -423,17 +473,11 @@ bool VulkanPipelineCache::ConfigurePipeline(
|
|||||||
reg::RB_DEPTHCONTROL normalized_depth_control,
|
reg::RB_DEPTHCONTROL normalized_depth_control,
|
||||||
uint32_t normalized_color_mask,
|
uint32_t normalized_color_mask,
|
||||||
VulkanRenderTargetCache::RenderPassKey render_pass_key,
|
VulkanRenderTargetCache::RenderPassKey render_pass_key,
|
||||||
VkPipeline& pipeline_out,
|
VulkanPipelineCache::Pipeline** pipeline_out) {
|
||||||
const PipelineLayoutProvider*& pipeline_layout_out) {
|
|
||||||
#if XE_GPU_FINE_GRAINED_DRAW_SCOPES
|
#if XE_GPU_FINE_GRAINED_DRAW_SCOPES
|
||||||
SCOPE_profile_cpu_f("gpu");
|
SCOPE_profile_cpu_f("gpu");
|
||||||
#endif // XE_GPU_FINE_GRAINED_DRAW_SCOPES
|
#endif // XE_GPU_FINE_GRAINED_DRAW_SCOPES
|
||||||
|
|
||||||
// Ensure shaders are translated - needed now for GetCurrentStateDescription.
|
|
||||||
if (!EnsureShadersTranslated(vertex_shader, pixel_shader)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
PipelineDescription description;
|
PipelineDescription description;
|
||||||
if (!GetCurrentStateDescription(
|
if (!GetCurrentStateDescription(
|
||||||
vertex_shader, pixel_shader, primitive_processing_result,
|
vertex_shader, pixel_shader, primitive_processing_result,
|
||||||
@@ -442,15 +486,13 @@ bool VulkanPipelineCache::ConfigurePipeline(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (last_pipeline_ && last_pipeline_->first == description) {
|
if (last_pipeline_ && last_pipeline_->first == description) {
|
||||||
pipeline_out = last_pipeline_->second.pipeline;
|
*pipeline_out = &last_pipeline_->second;
|
||||||
pipeline_layout_out = last_pipeline_->second.pipeline_layout;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
auto it = pipelines_.find(description);
|
auto it = pipelines_.find(description);
|
||||||
if (it != pipelines_.end()) {
|
if (it != pipelines_.end()) {
|
||||||
last_pipeline_ = &*it;
|
last_pipeline_ = &*it;
|
||||||
pipeline_out = it->second.pipeline;
|
*pipeline_out = &it->second;
|
||||||
pipeline_layout_out = it->second.pipeline_layout;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,19 +518,22 @@ bool VulkanPipelineCache::ConfigurePipeline(
|
|||||||
if (!pipeline_layout) {
|
if (!pipeline_layout) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkShaderModule geometry_shader = VK_NULL_HANDLE;
|
VkShaderModule geometry_shader = VK_NULL_HANDLE;
|
||||||
GeometryShaderKey geometry_shader_key;
|
if (description.geometry_shader != PipelineGeometryShader::kNone) {
|
||||||
if (GetGeometryShaderKey(
|
GeometryShaderKey geometry_shader_key;
|
||||||
description.geometry_shader,
|
GetGeometryShaderKey(
|
||||||
SpirvShaderTranslator::Modification(vertex_shader->modification()),
|
description.geometry_shader,
|
||||||
SpirvShaderTranslator::Modification(
|
SpirvShaderTranslator::Modification(vertex_shader->modification()),
|
||||||
pixel_shader ? pixel_shader->modification() : 0),
|
SpirvShaderTranslator::Modification(
|
||||||
geometry_shader_key)) {
|
pixel_shader ? pixel_shader->modification() : 0),
|
||||||
|
geometry_shader_key);
|
||||||
geometry_shader = GetGeometryShader(geometry_shader_key);
|
geometry_shader = GetGeometryShader(geometry_shader_key);
|
||||||
if (geometry_shader == VK_NULL_HANDLE) {
|
if (geometry_shader == VK_NULL_HANDLE) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VkRenderPass render_pass =
|
VkRenderPass render_pass =
|
||||||
render_target_cache_.GetPath() ==
|
render_target_cache_.GetPath() ==
|
||||||
RenderTargetCache::Path::kPixelShaderInterlock
|
RenderTargetCache::Path::kPixelShaderInterlock
|
||||||
@@ -498,9 +543,10 @@ bool VulkanPipelineCache::ConfigurePipeline(
|
|||||||
if (render_pass == VK_NULL_HANDLE) {
|
if (render_pass == VK_NULL_HANDLE) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
PipelineCreationArguments creation_arguments;
|
|
||||||
auto& pipeline =
|
auto& pipeline_pair =
|
||||||
*pipelines_.emplace(description, Pipeline(pipeline_layout)).first;
|
*pipelines_.emplace(description, Pipeline(pipeline_layout)).first;
|
||||||
|
|
||||||
// Get tessellation shaders if needed.
|
// Get tessellation shaders if needed.
|
||||||
VkShaderModule tessellation_vertex_shader = VK_NULL_HANDLE;
|
VkShaderModule tessellation_vertex_shader = VK_NULL_HANDLE;
|
||||||
VkShaderModule tessellation_control_shader = VK_NULL_HANDLE;
|
VkShaderModule tessellation_control_shader = VK_NULL_HANDLE;
|
||||||
@@ -523,19 +569,169 @@ bool VulkanPipelineCache::ConfigurePipeline(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
creation_arguments.pipeline = &pipeline;
|
// Pipeline hot-swap: When async mode is enabled, we have creation threads and
|
||||||
creation_arguments.vertex_shader = vertex_shader;
|
// a pixel shader, create a placeholder pipeline immediately (fast compile)
|
||||||
creation_arguments.pixel_shader = pixel_shader;
|
// and queue the real pipeline creation in the background. This reduces
|
||||||
creation_arguments.geometry_shader = geometry_shader;
|
// stutter from pipeline compilation.
|
||||||
creation_arguments.tessellation_vertex_shader = tessellation_vertex_shader;
|
bool use_async = cvars::async_shader_compilation &&
|
||||||
creation_arguments.tessellation_control_shader = tessellation_control_shader;
|
!creation_threads_.empty() && pixel_shader &&
|
||||||
creation_arguments.render_pass = render_pass;
|
placeholder_pixel_shader_ != VK_NULL_HANDLE;
|
||||||
if (!EnsurePipelineCreated(creation_arguments)) {
|
|
||||||
|
if (use_async) {
|
||||||
|
// Create placeholder pipeline immediately (uses simple PS, fast compile).
|
||||||
|
// Set is_placeholder BEFORE creating the pipeline to avoid race condition
|
||||||
|
// with the creation thread checking this flag.
|
||||||
|
pipeline_pair.second.is_placeholder.store(true, std::memory_order_release);
|
||||||
|
|
||||||
|
PipelineCreationArguments placeholder_args;
|
||||||
|
placeholder_args.pipeline = &pipeline_pair;
|
||||||
|
placeholder_args.vertex_shader = vertex_shader;
|
||||||
|
placeholder_args.pixel_shader = nullptr; // Will use placeholder PS
|
||||||
|
placeholder_args.geometry_shader = geometry_shader;
|
||||||
|
placeholder_args.tessellation_vertex_shader = tessellation_vertex_shader;
|
||||||
|
placeholder_args.tessellation_control_shader = tessellation_control_shader;
|
||||||
|
placeholder_args.render_pass = render_pass;
|
||||||
|
|
||||||
|
if (EnsurePipelineCreatedWithPlaceholder(placeholder_args)) {
|
||||||
|
// Queue real pipeline creation in background.
|
||||||
|
// Calculate priority based on whether shader writes to visible RTs.
|
||||||
|
uint8_t priority = 0;
|
||||||
|
if (pixel_shader) {
|
||||||
|
// Get bound RT mask from normalized_color_mask (4 bits per RT).
|
||||||
|
uint32_t bound_rts = (((normalized_color_mask >> 0) & 0xF) ? 1 : 0) |
|
||||||
|
(((normalized_color_mask >> 4) & 0xF) ? 2 : 0) |
|
||||||
|
(((normalized_color_mask >> 8) & 0xF) ? 4 : 0) |
|
||||||
|
(((normalized_color_mask >> 12) & 0xF) ? 8 : 0);
|
||||||
|
uint32_t shader_writes = pixel_shader->shader().writes_color_targets();
|
||||||
|
if (bound_rts & shader_writes) {
|
||||||
|
// Writes to at least one visible RT - high priority.
|
||||||
|
priority = 2;
|
||||||
|
// Extra priority if writing to RT0 (usually main color buffer).
|
||||||
|
if ((bound_rts & shader_writes) & 1) {
|
||||||
|
priority = 3;
|
||||||
|
}
|
||||||
|
} else if (pixel_shader->shader().writes_depth()) {
|
||||||
|
// Depth-only - medium priority.
|
||||||
|
priority = 1;
|
||||||
|
}
|
||||||
|
// else: writes to unbound RTs only - lowest priority (0).
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(creation_request_lock_);
|
||||||
|
PipelineCreationArguments creation_arguments;
|
||||||
|
creation_arguments.pipeline = &pipeline_pair;
|
||||||
|
creation_arguments.vertex_shader = vertex_shader;
|
||||||
|
creation_arguments.pixel_shader = pixel_shader;
|
||||||
|
creation_arguments.geometry_shader = geometry_shader;
|
||||||
|
creation_arguments.tessellation_vertex_shader =
|
||||||
|
tessellation_vertex_shader;
|
||||||
|
creation_arguments.tessellation_control_shader =
|
||||||
|
tessellation_control_shader;
|
||||||
|
creation_arguments.render_pass = render_pass;
|
||||||
|
creation_arguments.priority = priority;
|
||||||
|
creation_queue_.push(creation_arguments);
|
||||||
|
}
|
||||||
|
creation_request_cond_.notify_one();
|
||||||
|
} else {
|
||||||
|
// Placeholder creation failed, fall back to sync creation.
|
||||||
|
// Reset the flag we set earlier.
|
||||||
|
pipeline_pair.second.is_placeholder.store(false,
|
||||||
|
std::memory_order_release);
|
||||||
|
use_async = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!use_async) {
|
||||||
|
// Sync mode or no creation threads: create synchronously.
|
||||||
|
PipelineCreationArguments creation_arguments;
|
||||||
|
creation_arguments.pipeline = &pipeline_pair;
|
||||||
|
creation_arguments.vertex_shader = vertex_shader;
|
||||||
|
creation_arguments.pixel_shader = pixel_shader;
|
||||||
|
creation_arguments.geometry_shader = geometry_shader;
|
||||||
|
creation_arguments.tessellation_vertex_shader = tessellation_vertex_shader;
|
||||||
|
creation_arguments.tessellation_control_shader = tessellation_control_shader;
|
||||||
|
creation_arguments.render_pass = render_pass;
|
||||||
|
if (!EnsurePipelineCreated(creation_arguments)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
last_pipeline_ = &pipeline_pair;
|
||||||
|
*pipeline_out = &pipeline_pair.second;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanPipelineCache::EndSubmission() {
|
||||||
|
if (creation_threads_.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Await creation of all queued pipelines.
|
||||||
|
bool await_creation_completion_event;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(creation_request_lock_);
|
||||||
|
// Assuming the creation queue is already empty (because the processor
|
||||||
|
// thread also worked on creating the leftover pipelines), so only check
|
||||||
|
// if there are threads with pipelines currently being created.
|
||||||
|
await_creation_completion_event =
|
||||||
|
!creation_queue_.empty() || creation_threads_busy_ != 0;
|
||||||
|
if (await_creation_completion_event) {
|
||||||
|
creation_completion_event_->Reset();
|
||||||
|
creation_completion_set_event_.store(true, std::memory_order_release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (await_creation_completion_event) {
|
||||||
|
creation_request_cond_.notify_one();
|
||||||
|
xe::threading::Wait(creation_completion_event_.get(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanPipelineCache::IsCreatingPipelines() {
|
||||||
|
if (creation_threads_.empty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
pipeline_out = pipeline.second.pipeline;
|
std::lock_guard<std::mutex> lock(creation_request_lock_);
|
||||||
pipeline_layout_out = pipeline_layout;
|
return !creation_queue_.empty() || creation_threads_busy_ != 0;
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
void VulkanPipelineCache::CreationThread() {
|
||||||
|
for (;;) {
|
||||||
|
PipelineCreationArguments creation_arguments;
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(creation_request_lock_);
|
||||||
|
creation_request_cond_.wait(lock, [this]() {
|
||||||
|
return !creation_queue_.empty() || creation_threads_shutdown_;
|
||||||
|
});
|
||||||
|
if (creation_threads_shutdown_) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
creation_arguments = creation_queue_.top();
|
||||||
|
creation_queue_.pop();
|
||||||
|
++creation_threads_busy_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!EnsureShadersTranslated(creation_arguments.vertex_shader,
|
||||||
|
creation_arguments.pixel_shader)) {
|
||||||
|
// Mark pipeline as failed by keeping it as VK_NULL_HANDLE.
|
||||||
|
// The pipeline will remain VK_NULL_HANDLE, indicating failure.
|
||||||
|
XELOGE("Failed to translate shaders for pipeline creation");
|
||||||
|
} else {
|
||||||
|
if (!EnsurePipelineCreated(creation_arguments)) {
|
||||||
|
// Pipeline creation failed - it will remain VK_NULL_HANDLE.
|
||||||
|
XELOGE("Failed to create Vulkan pipeline");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(creation_request_lock_);
|
||||||
|
--creation_threads_busy_;
|
||||||
|
if (creation_completion_set_event_.load(std::memory_order_acquire) &&
|
||||||
|
creation_threads_busy_ == 0 && creation_queue_.empty()) {
|
||||||
|
creation_completion_set_event_.store(false, std::memory_order_release);
|
||||||
|
creation_completion_event_->Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VulkanPipelineCache::TranslateAnalyzedShader(
|
bool VulkanPipelineCache::TranslateAnalyzedShader(
|
||||||
@@ -2057,7 +2253,8 @@ VkShaderModule VulkanPipelineCache::GetTessellationVertexShader(
|
|||||||
|
|
||||||
bool VulkanPipelineCache::EnsurePipelineCreated(
|
bool VulkanPipelineCache::EnsurePipelineCreated(
|
||||||
const PipelineCreationArguments& creation_arguments) {
|
const PipelineCreationArguments& creation_arguments) {
|
||||||
if (creation_arguments.pipeline->second.pipeline != VK_NULL_HANDLE) {
|
if (creation_arguments.pipeline->second.pipeline.load(
|
||||||
|
std::memory_order_acquire) != VK_NULL_HANDLE) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2564,7 +2761,8 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
creation_arguments.pipeline->second.pipeline = pipeline;
|
creation_arguments.pipeline->second.pipeline.store(pipeline,
|
||||||
|
std::memory_order_release);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,15 +10,22 @@
|
|||||||
#ifndef XENIA_GPU_VULKAN_VULKAN_PIPELINE_STATE_CACHE_H_
|
#ifndef XENIA_GPU_VULKAN_VULKAN_PIPELINE_STATE_CACHE_H_
|
||||||
#define XENIA_GPU_VULKAN_VULKAN_PIPELINE_STATE_CACHE_H_
|
#define XENIA_GPU_VULKAN_VULKAN_PIPELINE_STATE_CACHE_H_
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <deque>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "xenia/base/hash.h"
|
#include "xenia/base/hash.h"
|
||||||
#include "xenia/base/platform.h"
|
#include "xenia/base/platform.h"
|
||||||
|
#include "xenia/base/threading.h"
|
||||||
#include "xenia/base/xxhash.h"
|
#include "xenia/base/xxhash.h"
|
||||||
#include "xenia/gpu/primitive_processor.h"
|
#include "xenia/gpu/primitive_processor.h"
|
||||||
#include "xenia/gpu/register_file.h"
|
#include "xenia/gpu/register_file.h"
|
||||||
@@ -39,8 +46,6 @@ class VulkanCommandProcessor;
|
|||||||
// implementations.
|
// implementations.
|
||||||
class VulkanPipelineCache {
|
class VulkanPipelineCache {
|
||||||
public:
|
public:
|
||||||
static constexpr size_t kLayoutUIDEmpty = 0;
|
|
||||||
|
|
||||||
class PipelineLayoutProvider {
|
class PipelineLayoutProvider {
|
||||||
public:
|
public:
|
||||||
virtual ~PipelineLayoutProvider() {}
|
virtual ~PipelineLayoutProvider() {}
|
||||||
@@ -50,6 +55,34 @@ class VulkanPipelineCache {
|
|||||||
PipelineLayoutProvider() = default;
|
PipelineLayoutProvider() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Pipeline {
|
||||||
|
std::atomic<VkPipeline> pipeline{VK_NULL_HANDLE};
|
||||||
|
// The layouts are owned by the VulkanCommandProcessor, and must not be
|
||||||
|
// destroyed by it while the pipeline cache is active.
|
||||||
|
const PipelineLayoutProvider* pipeline_layout;
|
||||||
|
|
||||||
|
Pipeline(const PipelineLayoutProvider* pipeline_layout_provider)
|
||||||
|
: pipeline_layout(pipeline_layout_provider) {}
|
||||||
|
|
||||||
|
// Copy constructor needed for unordered_map
|
||||||
|
Pipeline(const Pipeline& other)
|
||||||
|
: pipeline(other.pipeline.load(std::memory_order_acquire)),
|
||||||
|
pipeline_layout(other.pipeline_layout) {}
|
||||||
|
|
||||||
|
// Move constructor
|
||||||
|
Pipeline(Pipeline&& other) noexcept
|
||||||
|
: pipeline(other.pipeline.load(std::memory_order_acquire)),
|
||||||
|
pipeline_layout(other.pipeline_layout) {}
|
||||||
|
|
||||||
|
// Deleted copy assignment to prevent accidental copying
|
||||||
|
Pipeline& operator=(const Pipeline&) = delete;
|
||||||
|
|
||||||
|
// Deleted move assignment
|
||||||
|
Pipeline& operator=(Pipeline&&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr size_t kLayoutUIDEmpty = 0;
|
||||||
|
|
||||||
VulkanPipelineCache(VulkanCommandProcessor& command_processor,
|
VulkanPipelineCache(VulkanCommandProcessor& command_processor,
|
||||||
const RegisterFile& register_file,
|
const RegisterFile& register_file,
|
||||||
VulkanRenderTargetCache& render_target_cache,
|
VulkanRenderTargetCache& render_target_cache,
|
||||||
@@ -59,6 +92,9 @@ class VulkanPipelineCache {
|
|||||||
bool Initialize();
|
bool Initialize();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
|
void EndSubmission();
|
||||||
|
bool IsCreatingPipelines();
|
||||||
|
|
||||||
VulkanShader* LoadShader(xenos::ShaderType shader_type,
|
VulkanShader* LoadShader(xenos::ShaderType shader_type,
|
||||||
const uint32_t* host_address, uint32_t dword_count);
|
const uint32_t* host_address, uint32_t dword_count);
|
||||||
// Analyze shader microcode on the translator thread.
|
// Analyze shader microcode on the translator thread.
|
||||||
@@ -78,7 +114,6 @@ class VulkanPipelineCache {
|
|||||||
|
|
||||||
bool EnsureShadersTranslated(VulkanShader::VulkanTranslation* vertex_shader,
|
bool EnsureShadersTranslated(VulkanShader::VulkanTranslation* vertex_shader,
|
||||||
VulkanShader::VulkanTranslation* pixel_shader);
|
VulkanShader::VulkanTranslation* pixel_shader);
|
||||||
// TODO(Triang3l): Return a deferred creation handle.
|
|
||||||
bool ConfigurePipeline(
|
bool ConfigurePipeline(
|
||||||
VulkanShader::VulkanTranslation* vertex_shader,
|
VulkanShader::VulkanTranslation* vertex_shader,
|
||||||
VulkanShader::VulkanTranslation* pixel_shader,
|
VulkanShader::VulkanTranslation* pixel_shader,
|
||||||
@@ -86,8 +121,7 @@ class VulkanPipelineCache {
|
|||||||
reg::RB_DEPTHCONTROL normalized_depth_control,
|
reg::RB_DEPTHCONTROL normalized_depth_control,
|
||||||
uint32_t normalized_color_mask,
|
uint32_t normalized_color_mask,
|
||||||
VulkanRenderTargetCache::RenderPassKey render_pass_key,
|
VulkanRenderTargetCache::RenderPassKey render_pass_key,
|
||||||
VkPipeline& pipeline_out,
|
Pipeline** pipeline_out);
|
||||||
const PipelineLayoutProvider*& pipeline_layout_out);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class PipelineGeometryShader : uint32_t {
|
enum class PipelineGeometryShader : uint32_t {
|
||||||
@@ -218,26 +252,27 @@ class VulkanPipelineCache {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
struct Pipeline {
|
|
||||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
|
||||||
// The layouts are owned by the VulkanCommandProcessor, and must not be
|
|
||||||
// destroyed by it while the pipeline cache is active.
|
|
||||||
const PipelineLayoutProvider* pipeline_layout;
|
|
||||||
Pipeline(const PipelineLayoutProvider* pipeline_layout_provider)
|
|
||||||
: pipeline_layout(pipeline_layout_provider) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Description that can be passed from the command processor thread to the
|
|
||||||
// creation threads, with everything needed from caches pre-looked-up.
|
// creation threads, with everything needed from caches pre-looked-up.
|
||||||
struct PipelineCreationArguments {
|
struct PipelineCreationArguments {
|
||||||
std::pair<const PipelineDescription, Pipeline>* pipeline;
|
std::pair<const PipelineDescription, Pipeline>* pipeline;
|
||||||
const VulkanShader::VulkanTranslation* vertex_shader;
|
VulkanShader::VulkanTranslation* vertex_shader;
|
||||||
const VulkanShader::VulkanTranslation* pixel_shader;
|
VulkanShader::VulkanTranslation* pixel_shader;
|
||||||
VkShaderModule geometry_shader;
|
VkShaderModule geometry_shader;
|
||||||
// Tessellation shaders (only used when tessellation is active).
|
// Tessellation shaders (only used when tessellation is active).
|
||||||
VkShaderModule tessellation_vertex_shader; // VS for passing data to TCS.
|
VkShaderModule tessellation_vertex_shader; // VS for passing data to TCS.
|
||||||
VkShaderModule tessellation_control_shader; // TCS (hull shader).
|
VkShaderModule tessellation_control_shader; // TCS (hull shader).
|
||||||
VkRenderPass render_pass;
|
VkRenderPass render_pass;
|
||||||
|
// Priority for async compilation (higher = compiled sooner).
|
||||||
|
// Pipelines that write to visible render targets get higher priority.
|
||||||
|
uint8_t priority = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Comparator for priority queue - higher priority first.
|
||||||
|
struct PipelineCreationPriorityCompare {
|
||||||
|
bool operator()(const PipelineCreationArguments& a,
|
||||||
|
const PipelineCreationArguments& b) const {
|
||||||
|
return a.priority < b.priority; // max-heap: lower priority at bottom
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
union GeometryShaderKey {
|
union GeometryShaderKey {
|
||||||
@@ -374,8 +409,26 @@ class VulkanPipelineCache {
|
|||||||
pipelines_;
|
pipelines_;
|
||||||
|
|
||||||
// Previously used pipeline, to avoid lookups if the state wasn't changed.
|
// Previously used pipeline, to avoid lookups if the state wasn't changed.
|
||||||
const std::pair<const PipelineDescription, Pipeline>* last_pipeline_ =
|
std::pair<const PipelineDescription, Pipeline>* last_pipeline_ = nullptr;
|
||||||
nullptr;
|
|
||||||
|
void CreationThread();
|
||||||
|
|
||||||
|
// For asynchronous creation.
|
||||||
|
std::vector<std::unique_ptr<xe::threading::Thread>> creation_threads_;
|
||||||
|
std::atomic<bool> creation_threads_shutdown_{false};
|
||||||
|
std::atomic<size_t> creation_threads_busy_{0};
|
||||||
|
// Priority queue contains pointers to map entries. Pipelines are never
|
||||||
|
// evicted as games have a finite set that should all remain cached for
|
||||||
|
// performance. Higher priority pipelines (those writing to visible RTs)
|
||||||
|
// are compiled first.
|
||||||
|
std::priority_queue<PipelineCreationArguments,
|
||||||
|
std::vector<PipelineCreationArguments>,
|
||||||
|
PipelineCreationPriorityCompare>
|
||||||
|
creation_queue_;
|
||||||
|
std::mutex creation_request_lock_;
|
||||||
|
std::condition_variable creation_request_cond_;
|
||||||
|
std::unique_ptr<xe::threading::Event> creation_completion_event_ = nullptr;
|
||||||
|
std::atomic<bool> creation_completion_set_event_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace vulkan
|
} // namespace vulkan
|
||||||
|
|||||||
Reference in New Issue
Block a user