descriptors.cpp

cross platform rendering playground

src/backend/vulkan/descriptors.cpp

8.31 KB
#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