#include <volk.h>
#include "backend/vulkan/debug.h"
#include "backend/vulkan/utils.h"
#include "backend/vulkan/vk_api.h"
#include "shaders/renderer_types.h"
namespace vk {
// gave up on trying to somewhat unify this in a clean(ish) api
// its better if each platform writes as much backend code as possible for this, bindless or not,
// not sure if mutable stays here, just experimenting
void create_global_descriptors(Device &device) {
// descriptor pool
//
// set 0, two bindings: resource heap (mutable) + sampler heap.
// addressed only through DescriptorHandle fetches (rhi_abi.slang).
constexpr VkDescriptorPoolSize pool_sizes[] = {
{VK_DESCRIPTOR_TYPE_MUTABLE_EXT, MAX_RESOURCES},
{VK_DESCRIPTOR_TYPE_SAMPLER, MAX_SAMPLERS},
};
VkDescriptorPoolCreateInfo pool_info = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT | VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
.maxSets = 1,
.poolSizeCount = std::size(pool_sizes),
.pPoolSizes = pool_sizes,
};
VK_CHECK(vkCreateDescriptorPool(device.logical, &pool_info, nullptr, &device.descriptor_pool));
// descriptor set layout
// Binding 0: every image/buffer descriptor (General heap, mutable).
// Binding 1: every sampler descriptor (Sampler heap).
// Binding numbers must match rhi_abi.slang's fetch heaps.
constexpr VkDescriptorSetLayoutBinding bindings[] = {
{
.binding = VK_MUTABLE_BINDING,
.descriptorType = VK_DESCRIPTOR_TYPE_MUTABLE_EXT,
.descriptorCount = MAX_RESOURCES,
.stageFlags = VK_SHADER_STAGE_ALL,
},
{
.binding = VK_SAMPLER_BINDING,
.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER,
.descriptorCount = MAX_SAMPLERS,
.stageFlags = VK_SHADER_STAGE_ALL,
},
};
constexpr VkDescriptorBindingFlags binding_flags[std::size(bindings)] = {
VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT | VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT, // binding 0
VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT, // binding 1
};
// binding 0 is mutable so just the prep work for it
constexpr VkDescriptorType mutable_types[] = {
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER,
VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
};
VkMutableDescriptorTypeListEXT mutable_lists[std::size(bindings)];
mutable_lists[VK_MUTABLE_BINDING] = {
.descriptorTypeCount = std::size(mutable_types),
// everything in the list is valid, a single descriptor is padded out to
// be the size of the largest one
.pDescriptorTypes = mutable_types,
};
// still have to decalare in mutables
mutable_lists[VK_SAMPLER_BINDING] = {
.descriptorTypeCount = 0,
.pDescriptorTypes = nullptr, // empty, sampler is the only valid type
};
VkMutableDescriptorTypeCreateInfoEXT mutable_info{
.sType = VK_STRUCTURE_TYPE_MUTABLE_DESCRIPTOR_TYPE_CREATE_INFO_EXT,
.mutableDescriptorTypeListCount = std::size(mutable_lists),
.pMutableDescriptorTypeLists = mutable_lists,
};
VkDescriptorSetLayoutBindingFlagsCreateInfo flags_info{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO,
.pNext = &mutable_info,
.bindingCount = std::size(bindings),
.pBindingFlags = binding_flags,
};
VkDescriptorSetLayoutCreateInfo set_layout_info = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.pNext = &flags_info,
.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT,
.bindingCount = std::size(bindings),
.pBindings = bindings,
};
VK_CHECK(vkCreateDescriptorSetLayout(device.logical, &set_layout_info, nullptr, &device.descriptor_set_layout));
// descriptor sets
VkDescriptorSetAllocateInfo alloc_info = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
.descriptorPool = device.descriptor_pool,
.descriptorSetCount = 1,
.pSetLayouts = &device.descriptor_set_layout,
};
VK_CHECK(vkAllocateDescriptorSets(device.logical, &alloc_info, &device.descriptor_set));
VK_NAME_EX(device, device.descriptor_set, "bindless-global");
// pipeline layout, hopefully VK_EXT_Descriptor_heap soon, so I can get rid of this
VkPushConstantRange push_constant_range = {
.stageFlags = VK_SHADER_STAGE_ALL | VK_SHADER_STAGE_COMPUTE_BIT,
.offset = 0,
.size = 128,
};
VkPipelineLayoutCreateInfo pipeline_layout_info = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.setLayoutCount = 1,
.pSetLayouts = &device.descriptor_set_layout,
.pushConstantRangeCount = 1,
.pPushConstantRanges = &push_constant_range
};
VK_CHECK(vkCreatePipelineLayout(device.logical, &pipeline_layout_info, nullptr, &device.pipeline_layout));
}
void destroy_global_descriptors(rhi::Device device) {
vkDestroyPipelineLayout(device.logical, device.pipeline_layout, nullptr);
vkFreeDescriptorSets(device.logical, device.descriptor_pool, 1, &device.descriptor_set);
vkDestroyDescriptorPool(device.logical, device.descriptor_pool, nullptr);
vkDestroyDescriptorSetLayout(device.logical, device.descriptor_set_layout, nullptr);
device.pipeline_layout = VK_NULL_HANDLE;
device.descriptor_pool = VK_NULL_HANDLE;
device.descriptor_set_layout = VK_NULL_HANDLE;
device.descriptor_set = VK_NULL_HANDLE;
}
u64 write_sampler_descriptor(Device &device, Sampler sampler) {
u64 slot = SAMPLER_SLOTS.allocate();
if (slot == UINT64_MAX) {
return UINT64_MAX;
}
VkDescriptorImageInfo image_info = {};
image_info.sampler = sampler._handle;
VkWriteDescriptorSet write = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
write.dstSet = device.descriptor_set;
write.dstBinding = 1;
write.dstArrayElement = static_cast<u32>(slot);
write.descriptorCount = 1;
write.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
write.pImageInfo = &image_info;
vkUpdateDescriptorSets(device.logical, 1, &write, 0, nullptr);
return slot;
};
u64 write_image_view(Device &device, VkImageView view, bool storage) {
if (view == VK_NULL_HANDLE) {
return UINT64_MAX;
}
u64 slot = RESOURCE_SLOTS.allocate();
if (slot == UINT64_MAX) {
return UINT64_MAX;
}
VkDescriptorImageInfo image_info = {};
image_info.sampler = VK_NULL_HANDLE;
image_info.imageView = view;
image_info.imageLayout = storage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
VkWriteDescriptorSet write = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
write.dstSet = device.descriptor_set;
write.dstBinding = 0;
write.dstArrayElement = static_cast<u32>(slot);
write.descriptorCount = 1;
write.descriptorType = storage ? VK_DESCRIPTOR_TYPE_STORAGE_IMAGE : VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
write.pImageInfo = &image_info;
vkUpdateDescriptorSets(device.logical, 1, &write, 0, nullptr);
return slot;
};
u64 write_buffer_view(Device &device, rhi::Buffer *buffer, u64 offset, u64 size, bool writeable) {
if (buffer == nullptr || buffer->_handle == 0) {
return UINT64_MAX;
}
u64 slot = RESOURCE_SLOTS.allocate();
if (slot == UINT64_MAX) {
return UINT64_MAX;
}
VkDescriptorBufferInfo buffer_info = {};
buffer_info.buffer = buffer->_handle;
buffer_info.offset = offset;
buffer_info.range = (size == 0) ? VK_WHOLE_SIZE : size;
VkWriteDescriptorSet write = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
write.dstSet = device.descriptor_set;
write.dstBinding = 0;
write.dstArrayElement = static_cast<u32>(slot);
write.descriptorCount = 1;
write.descriptorType = writeable ? VK_DESCRIPTOR_TYPE_STORAGE_BUFFER : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
write.pBufferInfo = &buffer_info;
vkUpdateDescriptorSets(device.logical, 1, &write, 0, nullptr);
return slot;
};
void free_resource_slot(u64 slot) {
RESOURCE_SLOTS.free(slot);
}
void free_sampler_slot(u64 slot) {
SAMPLER_SLOTS.free(slot);
}
} // namespace vk