buffer.cpp

cross platform rendering playground

src/backend/vulkan/buffer.cpp

8.78 KB
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <vk_mem_alloc.h>
#include <volk.h>

#include "backend/vulkan/debug.h"
#include "backend/vulkan/utils.h"
#include "backend/vulkan/vk_api.h"
#include "backend/vulkan/vk_conversion.h"
#include "core/logger.h"

namespace rhi {
using namespace vk;

static inline VkBufferUsageFlags translate_buffer_usage(BufferUsage usage) {
    VkBufferUsageFlags flags = 0;
    u32 bits = static_cast<u32>(usage);
    if (bits & static_cast<u32>(BufferUsage::VertexBuffer)) {
        flags |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
    }
    if (bits & static_cast<u32>(BufferUsage::IndexBuffer)) {
        flags |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
    }
    if (bits & static_cast<u32>(BufferUsage::UniformBuffer)) {
        flags |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
    }
    if (bits & static_cast<u32>(BufferUsage::StorageBuffer)) {
        flags |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
    }
    if (bits & static_cast<u32>(BufferUsage::IndirectBuffer)) {
        flags |= VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT;
    }
    if (bits & static_cast<u32>(BufferUsage::CopySrc)) {
        flags |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
    }
    if (bits & static_cast<u32>(BufferUsage::CopyDst)) {
        flags |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
    }
    if (bits & static_cast<u32>(BufferUsage::ShaderAddr)) {
        flags |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
    }
    if (bits & static_cast<u32>(BufferUsage::AccelStructBuildInput)) {
        flags |= VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR;
    }
    if (bits & static_cast<u32>(BufferUsage::AccelStructStorage)) {
        flags |= VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR;
    }
    flags |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
    return flags;
}

struct VmaMemSelect {
    VmaMemoryUsage usage;
    VmaAllocationCreateFlags flags;
};

static inline VmaMemSelect vma_mem_select(const BufferDesc &desc) {
    VmaMemSelect out{VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0};
    switch (desc.memory) {
    case BufferMemType::Device:
        out.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
        break;
    case BufferMemType::Upload:
        out.usage = VMA_MEMORY_USAGE_AUTO;
        out.flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
        out.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
        break;
    case BufferMemType::Readback:
        out.usage = VMA_MEMORY_USAGE_AUTO;
        out.flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
        out.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
        break;
    }
    if (desc.dedicated) {
        out.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
    }
    return out;
}

bool create_buffer(Device &device, BufferDesc &desc, Buffer &buffer) {
    VkBufferCreateInfo bi{};
    bi.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    bi.size = desc.size;
    bi.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
    bi.usage = translate_buffer_usage(desc.usage);

    VmaAllocationCreateInfo mi{};
    const VmaMemSelect mem = vma_mem_select(desc);
    mi.usage = mem.usage;
    mi.flags = mem.flags;

    VmaAllocationInfo ai{};
    VkBuffer vk_buf;
    VK_CHECK(vmaCreateBuffer(device.memory.allocator, &bi, &mi, &vk_buf, &buffer._allocation, &ai));
    buffer._handle = vk_buf;
    if (desc.name.size() > 0) {
        VK_NAME_EX(device, vk_buf, desc.name.c_str());
    }

    if (Any(desc.usage, BufferUsage::ShaderAddr)) {
        VkBufferDeviceAddressInfo info{
            .sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
            .buffer = buffer._handle,
        };

        buffer.address = vkGetBufferDeviceAddress(device.logical, &info);
    }

    buffer.mapped = ai.pMappedData;
    buffer.desc = desc;

    return true;
}

static bool
create_vk_buffer_view(Device &device, const BufferViewDesc &desc, VkBufferView &out, BufferViewDesc &out_desc) {
    if (desc.buffer == nullptr || desc.buffer->_handle == 0) {
        return false;
    }
    out_desc = desc;
    if (out_desc.size == 0) {
        out_desc.size = desc.buffer->desc.size;
    }
    if (desc.type == BufferViewType::Texel) {
        VkBufferViewCreateInfo ci{};
        ci.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO;
        ci.buffer = desc.buffer->_handle;
        ci.format = VK_FORMAT_R32_UINT;
        ci.offset = desc.offset;
        ci.range = desc.size;
        VkBufferView vk_view;
        VK_CHECK(vkCreateBufferView(device.logical, &ci, nullptr, &vk_view));
        out = vk_view;
    } else {
        out = NON_TEXEL_BUFFER_VIEW_SENTINEL;
    }
    return true;
}

void destroy_buffer(Device &device, Buffer &buffer) {
    vmaDestroyBuffer(device.memory.allocator, buffer._handle, buffer._allocation);
    buffer = {};
}

u64 buffer_device_address(Device &device, Buffer &buffer, u64 offset) {
    if (offset > buffer.desc.size) {
        VEL_CRITICAL("buffer_device_address: offset {} exceeds buffer size {}", offset, buffer.desc.size);
        std::abort();
    }

    if (buffer.address == 0) {
        assert(Any(buffer.desc.usage, BufferUsage::ShaderAddr) && "ShaderAddr usage invalid");

        VkBufferDeviceAddressInfo info{
            .sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
            .buffer = buffer._handle,
        };

        buffer.address = vkGetBufferDeviceAddress(device.logical, &info);
    }

    return buffer.address + offset;
}

bool create_buffer_view(Device &device, const BufferViewDesc &desc, BufferView &out) {
    if (desc.buffer == nullptr || desc.buffer->_handle == 0) {
        return false;
    }
    switch (desc.type) {
    case BufferViewType::Storage:
        assert(Any(desc.buffer->desc.usage, BufferUsage::StorageBuffer) && "Storage view needs StorageBuffer usage");
        break;
    case BufferViewType::Uniform:
        assert(Any(desc.buffer->desc.usage, BufferUsage::UniformBuffer) && "Uniform view needs UniformBuffer usage");
        break;
    case BufferViewType::Vertex:
        assert(Any(desc.buffer->desc.usage, BufferUsage::VertexBuffer) && "Vertex view needs VertexBuffer usage");
        break;
    case BufferViewType::Index:
        assert(Any(desc.buffer->desc.usage, BufferUsage::IndexBuffer) && "Index view needs IndexBuffer usage");
        break;
    case BufferViewType::Indirect:
        assert(Any(desc.buffer->desc.usage, BufferUsage::IndirectBuffer) && "Indirect view needs IndirectBuffer usage");
        break;
    default:
        break;
    }
    BufferViewDesc full = desc;
    if (full.size == 0) {
        full.size = desc.buffer->desc.size;
    }
    VkBufferView vk_view = VK_NULL_HANDLE;
    BufferViewDesc out_desc{};
    if (!create_vk_buffer_view(device, full, vk_view, out_desc)) {
        return false;
    }
    const bool writeable = (full.type != BufferViewType::Uniform);
    u64 slot = vk::write_buffer_view(device, full.buffer, full.offset, full.size, writeable);
    if (slot == UINT64_MAX) {
        if (full.type == BufferViewType::Texel && vk_view != VK_NULL_HANDLE &&
            vk_view != NON_TEXEL_BUFFER_VIEW_SENTINEL) {
            vkDestroyBufferView(device.logical, vk_view, nullptr);
        }
        return false;
    }
    out.buffer = full.buffer;
    out.desc = out_desc;
    out._handle = vk_view;
    out.slot = slot;
    if (vk_view != VK_NULL_HANDLE && vk_view != NON_TEXEL_BUFFER_VIEW_SENTINEL && full.buffer != nullptr &&
        !full.buffer->desc.name.empty()) {
        char view_name[256];
        (void)snprintf(view_name, sizeof(view_name), "%s.view", full.buffer->desc.name.c_str());
        VK_NAME_EX(device, vk_view, view_name);
    }
    return true;
}

u64 handle_id(Device &device, BufferView &view) {
    (void)device;
    return view.slot;
}

void buffer_flush(Device &device, Buffer &buffer, u64 offset, u64 size) {
    if (buffer._allocation == VK_NULL_HANDLE) {
        return;
    }
    vmaFlushAllocation(device.memory.allocator, buffer._allocation, offset, size);
}

void buffer_invalidate(Device &device, Buffer &buffer, u64 offset, u64 size) {
    if (buffer._allocation == VK_NULL_HANDLE) {
        return;
    }
    vmaInvalidateAllocation(device.memory.allocator, buffer._allocation, offset, size);
}

void destroy_buffer_view(Device &device, BufferView &view) {
    if (view._handle != VK_NULL_HANDLE && view._handle != NON_TEXEL_BUFFER_VIEW_SENTINEL) {
        vkDestroyBufferView(device.logical, view._handle, nullptr);
    }
    view._handle = nullptr;
    if (view.slot != UINT64_MAX) {
        RESOURCE_SLOTS.free(view.slot);
        view.slot = UINT64_MAX;
    }
    view.buffer = nullptr;
    view.desc = {};
}

void destroy_buffer_views(Device &device, Buffer &buffer) {
    for (auto &[k, v] : buffer.view_cache) {
        if (k.type == BufferViewType::Texel && v != VK_NULL_HANDLE && v != NON_TEXEL_BUFFER_VIEW_SENTINEL) {
            vkDestroyBufferView(device.logical, v, nullptr);
        }
    }
    buffer.view_cache.clear();
}

} // namespace rhi