vk: Move descriptor management to the pipeline layer

- Frees up callers from managing descriptors themselves (ewww)
- Makes descriptor reuse possible
- Opens up the door to techniques like descriptor_buffer by abstracting away management to an implementation detail
This commit is contained in:
kd-11 2025-06-08 21:21:51 +03:00 committed by kd-11
parent 329655a1bf
commit 49729086ac
19 changed files with 677 additions and 539 deletions

View file

@ -1,5 +1,6 @@
#include "stdafx.h"
#include "VKProgramPipeline.h"
#include "VKResourceManager.h"
#include "vkutils/descriptors.h"
#include "vkutils/device.h"
@ -7,10 +8,61 @@
namespace vk
{
extern vk::render_device* get_current_renderer();
namespace glsl
{
using namespace ::glsl;
VkDescriptorType to_descriptor_type(program_input_type type)
{
switch (type)
{
case input_type_uniform_buffer:
return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
case input_type_texel_buffer:
return VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
case input_type_texture:
return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
case input_type_storage_buffer:
return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
case input_type_storage_texture:
return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
default:
fmt::throw_exception("Unexpected program input type %d", static_cast<int>(type));
}
}
VkShaderStageFlags to_shader_stage_flags(::glsl::program_domain domain)
{
switch (domain)
{
case glsl_vertex_program:
return VK_SHADER_STAGE_VERTEX_BIT;
case glsl_fragment_program:
return VK_SHADER_STAGE_FRAGMENT_BIT;
case glsl_compute_program:
return VK_SHADER_STAGE_COMPUTE_BIT;
default:
fmt::throw_exception("Unexpected domain %d", static_cast<int>(domain));
}
}
const char* to_string(::glsl::program_domain domain)
{
switch (domain)
{
case glsl_vertex_program:
return "vertex";
case glsl_fragment_program:
return "fragment";
case glsl_compute_program:
return "compute";
default:
fmt::throw_exception("Unexpected domain %d", static_cast<int>(domain));
}
}
void shader::create(::glsl::program_domain domain, const std::string& source)
{
type = domain;
@ -23,11 +75,8 @@ namespace vk
if (!spirv::compile_glsl_to_spv(m_compiled, m_source, type, ::glsl::glsl_rules_vulkan))
{
const std::string shader_type = type == ::glsl::program_domain::glsl_vertex_program ? "vertex" :
type == ::glsl::program_domain::glsl_fragment_program ? "fragment" : "compute";
rsx_log.notice("%s", m_source);
fmt::throw_exception("Failed to compile %s shader", shader_type);
fmt::throw_exception("Failed to compile %s shader", to_string(type));
}
VkShaderModuleCreateInfo vs_info;
@ -69,34 +118,56 @@ namespace vk
return m_handle;
}
void program::create_impl()
void program::init()
{
linked = false;
attribute_location_mask = 0;
vertex_attributes_mask = 0;
fs_texture_bindings.fill(~0u);
fs_texture_mirror_bindings.fill(~0u);
vs_texture_bindings.fill(~0u);
}
program::program(VkDevice dev, VkPipeline p, VkPipelineLayout layout, const std::vector<program_input> &vertex_input, const std::vector<program_input>& fragment_inputs)
: m_device(dev), pipeline(p), pipeline_layout(layout)
program::program(VkDevice dev, const VkGraphicsPipelineCreateInfo& create_info, const std::vector<program_input> &vertex_inputs, const std::vector<program_input>& fragment_inputs)
: m_device(dev)
{
create_impl();
load_uniforms(vertex_input);
init();
load_uniforms(vertex_inputs);
load_uniforms(fragment_inputs);
create_pipeline_layout();
ensure(m_pipeline_layout);
auto _create_info = create_info;
_create_info.layout = m_pipeline_layout;
CHECK_RESULT(vkCreateGraphicsPipelines(dev, nullptr, 1, &create_info, nullptr, &m_pipeline));
}
program::program(VkDevice dev, VkPipeline p, VkPipelineLayout layout)
: m_device(dev), pipeline(p), pipeline_layout(layout)
program::program(VkDevice dev, const VkComputePipelineCreateInfo& create_info, const std::vector<program_input>& compute_inputs)
: m_device(dev)
{
create_impl();
init();
load_uniforms(compute_inputs);
create_pipeline_layout();
ensure(m_pipeline_layout);
auto _create_info = create_info;
_create_info.layout = m_pipeline_layout;
CHECK_RESULT(vkCreateComputePipelines(dev, nullptr, 1, &create_info, nullptr, &m_pipeline));
}
program::~program()
{
vkDestroyPipeline(m_device, pipeline, nullptr);
vkDestroyPipeline(m_device, m_pipeline, nullptr);
if (m_pipeline_layout)
{
vkDestroyPipelineLayout(m_device, m_pipeline_layout, nullptr);
vkDestroyDescriptorSetLayout(m_device, m_descriptor_set_layout, nullptr);
vk::get_resource_manager()->dispose(m_descriptor_pool);
}
}
program& program::load_uniforms(const std::vector<program_input>& inputs)
@ -160,14 +231,36 @@ namespace vk
});
}
void program::bind_uniform(const VkDescriptorImageInfo &image_descriptor, const std::string& uniform_name, VkDescriptorType type, vk::descriptor_set &set)
u32 program::get_uniform_location(program_input_type type, const std::string& uniform_name)
{
const auto& uniform = uniforms[type];
const auto result = std::find_if(uniform.cbegin(), uniform.cend(), [&uniform_name](const auto& u)
{
return u.name == uniform_name;
});
if (result == uniform.end())
{
return { umax };
}
return result->location;
}
void program::bind_uniform(const VkDescriptorImageInfo &image_descriptor, const std::string& uniform_name, VkDescriptorType type)
{
for (const auto &uniform : uniforms[program_input_type::input_type_texture])
{
if (uniform.name == uniform_name)
{
set.push(image_descriptor, type, uniform.location);
attribute_location_mask |= (1ull << uniform.location);
if (m_descriptor_slots[uniform.location].matches(image_descriptor))
{
return;
}
next_descriptor_set();
m_descriptor_set.push(image_descriptor, type, uniform.location);
m_descriptors_dirty[uniform.location] = false;
return;
}
}
@ -175,7 +268,7 @@ namespace vk
rsx_log.notice("texture not found in program: %s", uniform_name.c_str());
}
void program::bind_uniform(const VkDescriptorImageInfo & image_descriptor, int texture_unit, ::glsl::program_domain domain, vk::descriptor_set &set, bool is_stencil_mirror)
void program::bind_uniform(const VkDescriptorImageInfo & image_descriptor, int texture_unit, ::glsl::program_domain domain, bool is_stencil_mirror)
{
ensure(domain != ::glsl::program_domain::glsl_compute_program);
@ -189,34 +282,46 @@ namespace vk
binding = vs_texture_bindings[texture_unit];
}
if (binding != ~0u)
if (binding == ~0u) [[ unlikely ]]
{
set.push(image_descriptor, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, binding);
attribute_location_mask |= (1ull << binding);
rsx_log.notice("texture not found in program: %stex%u", (domain == ::glsl::program_domain::glsl_vertex_program) ? "v" : "", texture_unit);
return;
}
rsx_log.notice("texture not found in program: %stex%u", (domain == ::glsl::program_domain::glsl_vertex_program)? "v" : "", texture_unit);
if (m_descriptor_slots[binding].matches(image_descriptor))
{
return;
}
next_descriptor_set();
m_descriptor_set.push(image_descriptor, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, binding);
m_descriptors_dirty[binding] = false;
}
void program::bind_uniform(const VkDescriptorBufferInfo &buffer_descriptor, u32 binding_point, vk::descriptor_set &set)
void program::bind_uniform(const VkDescriptorBufferInfo &buffer_descriptor, u32 binding_point)
{
bind_buffer(buffer_descriptor, binding_point, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, set);
bind_buffer(buffer_descriptor, binding_point, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
}
void program::bind_uniform(const VkBufferView &buffer_view, u32 binding_point, vk::descriptor_set &set)
void program::bind_uniform(const VkBufferView &buffer_view, u32 binding_point)
{
set.push(buffer_view, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, binding_point);
attribute_location_mask |= (1ull << binding_point);
if (m_descriptor_slots[binding_point].matches(buffer_view))
{
return;
}
next_descriptor_set();
m_descriptor_set.push(buffer_view, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, binding_point);
m_descriptors_dirty[binding_point] = false;
}
void program::bind_uniform(const VkBufferView &buffer_view, program_input_type type, const std::string &binding_name, vk::descriptor_set &set)
void program::bind_uniform(const VkBufferView &buffer_view, program_input_type type, const std::string &binding_name)
{
for (const auto &uniform : uniforms[type])
{
if (uniform.name == binding_name)
{
bind_uniform(buffer_view, uniform.location, set);
bind_uniform(buffer_view, uniform.location);
return;
}
}
@ -224,10 +329,135 @@ namespace vk
rsx_log.notice("vertex buffer not found in program: %s", binding_name.c_str());
}
void program::bind_buffer(const VkDescriptorBufferInfo &buffer_descriptor, u32 binding_point, VkDescriptorType type, vk::descriptor_set &set)
void program::bind_buffer(const VkDescriptorBufferInfo &buffer_descriptor, u32 binding_point, VkDescriptorType type)
{
set.push(buffer_descriptor, type, binding_point);
attribute_location_mask |= (1ull << binding_point);
m_descriptor_set.push(buffer_descriptor, type, binding_point);
m_descriptors_dirty[binding_point] = false;
}
VkDescriptorSet program::allocate_descriptor_set()
{
if (!m_descriptor_pool)
{
create_descriptor_pool();
}
return m_descriptor_pool->allocate(m_descriptor_set_layout);
}
void program::next_descriptor_set()
{
const auto new_set = allocate_descriptor_set();
const auto old_set = m_descriptor_set.value();
if (old_set)
{
m_copy_cmds.clear();
for (unsigned i = 0; i < m_copy_cmds.size(); ++i)
{
if (!m_descriptors_dirty[i])
{
continue;
}
// Reuse already initialized memory. Each command is the same anyway.
m_copy_cmds.resize(m_copy_cmds.size() + 1);
auto& cmd = m_copy_cmds.back();
cmd.srcBinding = cmd.dstBinding = i;
cmd.srcSet = old_set;
cmd.dstSet = new_set;
}
m_descriptor_set.push(m_copy_cmds);
}
m_descriptor_set = allocate_descriptor_set();
}
program& program::bind(const vk::command_buffer& cmd, VkPipelineBindPoint bind_point)
{
VkDescriptorSet set = m_descriptor_set.value();
vkCmdBindPipeline(cmd, bind_point, m_pipeline);
vkCmdBindDescriptorSets(cmd, bind_point, m_pipeline_layout, 0, 1, &set, 0, nullptr);
return *this;
}
void program::create_descriptor_set_layout()
{
ensure(m_descriptor_set_layout == VK_NULL_HANDLE);
rsx::simple_array<VkDescriptorSetLayoutBinding> bindings;
bindings.reserve(16);
m_descriptor_pool_sizes.clear();
m_descriptor_pool_sizes.reserve(input_type_max_enum);
for (const auto& type_arr : uniforms)
{
if (type_arr.empty() || type_arr.front().type == input_type_push_constant)
{
continue;
}
VkDescriptorType type = to_descriptor_type(type_arr.front().type);
m_descriptor_pool_sizes.push_back({ .type = type });
for (const auto& input : type_arr)
{
VkDescriptorSetLayoutBinding binding
{
.binding = input.location,
.descriptorType = type,
.descriptorCount = 1,
.stageFlags = to_shader_stage_flags(input.domain)
};
bindings.push_back(binding);
m_descriptor_pool_sizes.back().descriptorCount++;
}
}
VkDescriptorSetLayoutCreateInfo set_layout_create_info
{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.flags = 0,
.bindingCount = ::size32(bindings),
.pBindings = bindings.data()
};
CHECK_RESULT(vkCreateDescriptorSetLayout(m_device, &set_layout_create_info, nullptr, &m_descriptor_set_layout));
}
void program::create_pipeline_layout()
{
ensure(!linked);
ensure(m_pipeline_layout == VK_NULL_HANDLE);
create_descriptor_set_layout();
rsx::simple_array<VkPushConstantRange> push_constants{};
for (const auto& input : uniforms[input_type_push_constant])
{
const auto& range = input.as_push_constant();
push_constants.push_back({ .offset = range.offset, .size = range.size });
}
VkPipelineLayoutCreateInfo create_info
{
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.flags = 0,
.setLayoutCount = 1,
.pSetLayouts = &m_descriptor_set_layout,
.pushConstantRangeCount = ::size32(push_constants),
.pPushConstantRanges = push_constants.data()
};
CHECK_RESULT(vkCreatePipelineLayout(m_device, &create_info, nullptr, &m_pipeline_layout));
}
void program::create_descriptor_pool()
{
ensure(linked);
m_descriptor_pool = std::make_unique<descriptor_pool>();
m_descriptor_pool->create(*vk::get_current_renderer(), m_descriptor_pool_sizes);
}
}
}