#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