diff options
Diffstat (limited to 'unused code')
| -rw-r--r-- | unused code/rend_vk_buffer.c | 116 | ||||
| -rw-r--r-- | unused code/rend_vk_command_queue.c | 153 | ||||
| -rw-r--r-- | unused code/rend_vk_memory.c | 100 | ||||
| -rw-r--r-- | unused code/rend_vk_pool.c | 221 | ||||
| -rw-r--r-- | unused code/rend_vk_sbta.c | 331 |
5 files changed, 921 insertions, 0 deletions
diff --git a/unused code/rend_vk_buffer.c b/unused code/rend_vk_buffer.c new file mode 100644 index 0000000..37f3e06 --- /dev/null +++ b/unused code/rend_vk_buffer.c @@ -0,0 +1,116 @@ +#pragma once +#include "rend_internal.h" +#include "rend_vk_internal.h" +#include <vulkan/vulkan_core.h> + +#if 0 +static RendBuffer +rend_vk_buffer_create(VkDevice logical_device, VkAllocationCallbacks *allocator, VkDeviceSize size, VkBufferUsageFlags usage, uint32_t *family_indices, uint32_t family_indices_count) +{ + RendBuffer buffer = { + .handle = 0, + .logical_device = logical_device, + .memory = NULL, + .allocator = allocator, + .usage = usage | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, + }; + + VkBufferCreateInfo buffer_info = { + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .size = size, + .usage = usage, + .sharingMode = (family_indices_count > 1) ? VK_SHARING_MODE_CONCURRENT : VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = family_indices_count, + .pQueueFamilyIndices = family_indices + }; + + vkCreateBuffer(logical_device, &buffer_info, allocator, &buffer.handle); + return buffer; +} + +static uint32_t +rend_vk_buffer_required_memory_type(RendBuffer *buffer) +{ + assert(buffer->memory == NULL && "Buffer already bound to memory"); + VkMemoryRequirements mem_requirements; + vkGetBufferMemoryRequirements(buffer->logical_device, buffer->handle, &mem_requirements); + return mem_requirements.memoryTypeBits; +} + +static void +rend_vk_buffer_bind_memory(RendBuffer *buffer, RendVkMemory *memory) +{ + assert(buffer->memory == NULL && "Buffer already bound to memory"); + vkBindBufferMemory(buffer->logical_device, buffer->handle, memory->device_memory, 0); + buffer->memory = memory; +} + +static void +rend_vk_buffer_destroy(RendBuffer *buffer) +{ + assert(buffer && buffer->handle != 0); + vkDestroyBuffer(buffer->logical_device, buffer->handle, buffer->allocator); + buffer->handle = 0; + buffer->memory = 0; +} + +static void +rend_vk_buffer_copy_device(VkQueue queue, VkCommandPool pool, RendBuffer *dest, size_t dest_offset, size_t bytes, RendBuffer *src, size_t src_offset, VkFence fence) +{ + assert(src && dest); // check that im not sending null pointers + assert(src->usage & VK_BUFFER_USAGE_TRANSFER_SRC_BIT); // source buffer must be marked as transfer src + assert(dest->usage & VK_BUFFER_USAGE_TRANSFER_DST_BIT); // dest buffer must be marked as transfer dest + + VkCommandBuffer transfer_cmd = VK_NULL_HANDLE; + + VkCommandBufferAllocateInfo alloc_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandBufferCount = 1, + .commandPool = pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .pNext = NULL, + }; + + vkAllocateCommandBuffers(dest->logical_device, &alloc_info, &transfer_cmd); + + VkCommandBufferBeginInfo begin_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + }; + vkBeginCommandBuffer(transfer_cmd, &begin_info); + + VkBufferCopy buffer_copy = { + .srcOffset = (VkDeviceSize)src_offset, + .dstOffset = (VkDeviceSize)dest_offset, + .size = (VkDeviceSize)bytes, + }; + + vkCmdCopyBuffer(transfer_cmd, src->handle, dest->handle, 1, &buffer_copy); + vkEndCommandBuffer(transfer_cmd); + + VkCommandBufferSubmitInfo cmd_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .commandBuffer = transfer_cmd, + .deviceMask = 0, + }; + + VkSubmitInfo2 submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, + .commandBufferInfoCount = 1, + .pCommandBufferInfos = &cmd_info, + }; + + vkQueueSubmit2(queue, 1, &submit_info, fence); + vkQueueWaitIdle(queue); + vkFreeCommandBuffers(dest->logical_device, pool, 1, &transfer_cmd); +} + +static uint64_t +rend_vk_buffer_address(RendBuffer *buffer, VkDevice device) +{ + VkBufferDeviceAddressInfoKHR address_info = {VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_KHR}; + address_info.buffer = buffer->handle; + VkDeviceAddress address = vkGetBufferDeviceAddress(device, &address_info); + return (uint64_t) address; +} +#endif diff --git a/unused code/rend_vk_command_queue.c b/unused code/rend_vk_command_queue.c new file mode 100644 index 0000000..1ddd780 --- /dev/null +++ b/unused code/rend_vk_command_queue.c @@ -0,0 +1,153 @@ +#pragma once +#include "rend_internal.h" +#include "rend_vk_internal.h" +#include <vulkan/vulkan_core.h> +#include <stdatomic.h> +#include <stdlib.h> + +/* DEFERRED COMMANDS: + * Tracks single-use command buffers submitted for async GPU work, + * so callers don't have to block (vkQueueWaitIdle) to know when it's + * safe to free/reset them. One shared timeline semaphore; every + * submission claims the next monotonic value. + */ + +struct RendVkPendingCmd { + VkCommandPool pool; + VkCommandBuffer cmd; + uint64_t wait_value; +}; + +struct RendVkDeferredCmds { + VkSemaphore timeline; + RendVkPendingCmd *darray; + size_t count; + size_t capacity; +}; + +#define RENDER_VK_DEFERRED_CMDS_INITIAL_CAPACITY 16 + +static inline RendVkDeferredCmds +rend_vk_cmdbuf_deferred_create(void) +{ + RendVkDeferredCmds tracker = {0}; + + VkSemaphoreTypeCreateInfo type_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO, + .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE, + .initialValue = 0, + }; + VkSemaphoreCreateInfo sem_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = &type_info, + }; + vkCreateSemaphore(vk_device.logical_device, &sem_info, vk_allocator, &tracker.timeline); + + tracker.capacity = RENDER_VK_DEFERRED_CMDS_INITIAL_CAPACITY; + tracker.darray = rmalloc(sizeof(RendVkPendingCmd) * tracker.capacity); + tracker.count = 0; + + return tracker; +} + +static inline void +rend_vk_cmdbuf_deferred_destroy(RendVkDeferredCmds *tracker) +{ + vkDestroySemaphore(vk_device.logical_device, tracker->timeline, vk_allocator); + rfree(tracker->darray); + *tracker = (RendVkDeferredCmds) {0}; +} + +static inline void rend_vk_cmdbuf_deferred_lock(RendVkDeferredCmds *t) { (void)t; } +static inline void rend_vk_cmdbuf_deferred_unlock(RendVkDeferredCmds *t) { (void)t; } + +static inline VkCommandBuffer +rend_vk_cmdbuf_deferred_begin(VkCommandPool pool) +{ + VkCommandBufferAllocateInfo alloc_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandPool = pool, + .commandBufferCount = 1, + }; + + VkCommandBuffer cmd; + vkAllocateCommandBuffers(vk_device.logical_device, &alloc_info, &cmd); + + VkCommandBufferBeginInfo begin = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + }; + + vkBeginCommandBuffer(cmd, &begin); + return cmd; +} + +static inline void +rend_vk_cmdbuf_deferred_push(RendVkDeferredCmds *tracker, VkCommandPool pool, VkCommandBuffer cmd, uint64_t wait_value) +{ + rend_vk_cmdbuf_deferred_lock(tracker); + if (tracker->count == tracker->capacity) { + tracker->capacity *= 2; + tracker->darray = realloc(tracker->darray, sizeof(RendVkPendingCmd) * tracker->capacity); + } + tracker->darray[tracker->count++] = (RendVkPendingCmd) { + .pool = pool, + .cmd = cmd, + .wait_value = wait_value, + }; + rend_vk_cmdbuf_deferred_unlock(tracker); +} + +static inline uint64_t +rend_vk_cmdbuf_deferred_submit(RendVkDeferredCmds *tracker, VkCommandPool pool, VkCommandBuffer cmd, VkQueue queue, uint64_t wait_value, VkPipelineStageFlags2 wait_stage, VkPipelineStageFlags2 signal_stage) +{ + uint64_t signal_value = wait_value + 1; + + VkCommandBufferSubmitInfo cmd_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .commandBuffer = cmd, + }; + VkSemaphoreSubmitInfo wait_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .semaphore = tracker->timeline, + .value = wait_value, + .stageMask = wait_stage, + }; + VkSemaphoreSubmitInfo signal_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .semaphore = tracker->timeline, + .value = signal_value, + .stageMask = signal_stage, + }; + VkSubmitInfo2 submit = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, + .waitSemaphoreInfoCount = wait_value ? 1 : 0, + .pWaitSemaphoreInfos = &wait_info, + .commandBufferInfoCount = 1, + .pCommandBufferInfos = &cmd_info, + .signalSemaphoreInfoCount = 1, + .pSignalSemaphoreInfos = &signal_info, + }; + + vkQueueSubmit2(queue, 1, &submit, VK_NULL_HANDLE); + + rend_vk_cmdbuf_deferred_push(tracker, pool, cmd, signal_value); + return signal_value; +} + +static inline void +rend_vk_cmdbuf_deferred_flush(RendVkDeferredCmds *tracker) +{ + uint64_t completed; + vkGetSemaphoreCounterValue(vk_device.logical_device, tracker->timeline, &completed); + + for (size_t i = 0; i < tracker->count; ) { + if (completed >= tracker->darray[i].wait_value) { + vkFreeCommandBuffers(vk_device.logical_device, tracker->darray[i].pool, 1, &tracker->darray[i].cmd); + tracker->darray[i] = tracker->darray[--tracker->count]; + } else { + i++; + } + } +} diff --git a/unused code/rend_vk_memory.c b/unused code/rend_vk_memory.c new file mode 100644 index 0000000..4782b46 --- /dev/null +++ b/unused code/rend_vk_memory.c @@ -0,0 +1,100 @@ +#pragma once + +#include "rend_internal.h" +#include "rend_vk_internal.h" +#include <vulkan/vulkan_core.h> + + +#if 0 +static RendVkMemory +rend_vk_memory_malloc(size_t size, VkDevice logical_device, VkPhysicalDevice physical_device, uint32_t heap_index, VkAllocationCallbacks *allocator) +{ + RendVkMemory memory = { + .host_mapped_memory = NULL, + .device_memory = 0, + .logical_device = logical_device, + .heap_index = heap_index + }; + + VkMemoryAllocateFlagsInfo flags_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO, + .pNext = NULL, + .flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT, /* REQUIRED for BDA buffers */ + .deviceMask = 0 + }; + + VkMemoryAllocateInfo alloc_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = size, + .pNext = &flags_info, + .memoryTypeIndex = heap_index + }; + + vkAllocateMemory(logical_device, &alloc_info, allocator, &memory.device_memory); + return memory; +} + +static void +rend_vk_memory_free(RendVkMemory *memory) +{ + if (memory->host_mapped_memory) { + rend_vk_memory_unmap(memory); + } + if (memory->offset == 0) { + vkFreeMemory(memory->logical_device, memory->device_memory, memory->allocator); + memset(memory, 0, sizeof *memory); + } else { + PWARN("[REND_VK] Attempted to free memory with an offset!"); + } +} + +static void* +rend_vk_memory_map(RendVkMemory *memory) +{ + // SPEC: memory must have been created with a memory type that reports VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT + // vkMapMemory will fail if the implementation is unable to allocate an appropriately sized contiguous virtual address range, + // e.g. due to virtual address space fragmentation or platform limits. + // In such cases, vkMapMemory must return VK_ERROR_MEMORY_MAP_FAILED. + // The application can improve the likelihood of success by reducing the size of the mapped range and/or removing unneeded mappings using vkUnmapMemory. + + vkMapMemory(memory->logical_device, memory->device_memory, memory->offset, memory->size, 0, &memory->host_mapped_memory); + return memory->host_mapped_memory; +} + +static void +rend_vk_memory_unmap(RendVkMemory *memory) +{ + if (memory->offset == 0) { + vkUnmapMemory(memory->logical_device, memory->device_memory); + memory->host_mapped_memory = 0; + } else { + PWARN("[REND_VK] Attempted to unmap memory with an offset!"); + } +} + +static void +rend_vk_memory_copy(RendVkMemory *memory, size_t offset, const void *data, size_t size) +{ + /* bounds check */ + if (offset + size > memory->size) { + REND__WARN("Memory write out of bounds!"); + return; + } + + /* important to cast this */ + uint8_t *dest = memory->host_mapped_memory; + memcpy(dest + offset, data, size); + + /* flush host writes if memory is non-coherent */ + // VkMappedMemoryRange range = { + // .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, + // .memory = memory->device_memory, + // .offset = offset, + // .size = size + // }; + // + // vkFlushMappedMemoryRanges(memory->logical_device, 1, &range); + +} + +#endif diff --git a/unused code/rend_vk_pool.c b/unused code/rend_vk_pool.c new file mode 100644 index 0000000..e527354 --- /dev/null +++ b/unused code/rend_vk_pool.c @@ -0,0 +1,221 @@ +#pragma once +#include "rend_internal.h" +#include "rend_vk_internal.h" +#include <vulkan/vulkan_core.h> + +/* NOTE(vasco): + * Check out https://kylehalladay.com/blog/tutorial/2017/12/13/Custom-Allocators-Vulkan.html + * for a basic grasp on what it entails to actually write a memory allocator for vulkan. + * + * This allocator isnt anything particularly amazing but it works so I'm keeping this code. + * Based on the framework presented here we could implement other more efficient allocators. + * Which is what I did for the arena allocator. + */ + +#if 0 +typedef struct RendVkAddress { + VkDeviceMemory handle; + uint32_t type; + uint32_t id; + VkDeviceSize size; + VkDeviceSize offset; +} RendVkAddress; + +typedef struct RendVkPoolLayout { + uint64_t offset, size; +} RendVkPoolLayout; + +typedef struct RendVKPoolBlock { + RendVkAddress address; + RendVkPoolLayout *layout_darr; + uint8_t reserved; +} RendVKPoolBlock; + +typedef struct RendVkPoolMemory { + RendVKPoolBlock *block_darr; +} RendVkPoolMemory; + +struct RendVkPoolAllocator { + VkAllocationCallbacks *allocator; + size_t *mem_type_alloc_sizes; // allocation size per type of memory available on the gpu + RendVkPoolMemory *mem_pools; // memory pools per type of memory available on the gpu + VkDevice logical_device; // virtual device + VkPhysicalDevice physical_device; // physical device we are allocating memory from + VkDeviceSize page_size; // allocations must respect the physical limitations of the gpu + VkDeviceSize block_min_size; // minimum size per block of memory + uint64_t total_allocations; + uint32_t memory_type_count; +}; + +static RendVkPoolAllocator +rend_vk_pool_create(VkDevice logical_device, VkPhysicalDevice physical_device, VkPhysicalDeviceLimits device_limits, VkAllocationCallbacks *allocator) +{ + RendVkPoolAllocator pool = { + .allocator = allocator, + .mem_type_alloc_sizes = NULL, + .mem_pools = NULL, + .logical_device = logical_device, + .physical_device = physical_device, + .page_size = 0, + .block_min_size = 0, + .total_allocations = 0, + .memory_type_count = 0, + }; + + VkPhysicalDeviceMemoryProperties mem_properties; + vkGetPhysicalDeviceMemoryProperties(physical_device, &mem_properties); + + pool.mem_type_alloc_sizes = malloc(mem_properties.memoryTypeCount * sizeof *pool.mem_type_alloc_sizes); + memset(pool.mem_type_alloc_sizes, 0, mem_properties.memoryTypeCount * sizeof *pool.mem_type_alloc_sizes); + + pool.mem_pools = malloc(mem_properties.memoryTypeCount * sizeof *pool.mem_pools); + memset(pool.mem_pools, 0, mem_properties.memoryTypeCount * sizeof *pool.mem_pools); + + pool.memory_type_count = mem_properties.memoryTypeCount; + pool.page_size = device_limits.bufferImageGranularity; + pool.block_min_size = pool.page_size * 10; + + return pool; +} + +static uint32_t +rend_vk_pool_add_block(RendVkPoolAllocator *pool, VkDeviceSize size, uint32_t memory_type, VkMemoryPropertyFlags properties, bool fit_to_alloc) +{ + VkDeviceSize new_pool_size = size * 2; + new_pool_size = (new_pool_size < pool->block_min_size) ? pool->block_min_size : new_pool_size; + + VkMemoryAllocateInfo alloc_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = new_pool_size, + .memoryTypeIndex = rend_vk_memory_find_index(pool->physical_device, memory_type, properties) + }; + + RendVKPoolBlock block = {0}; + VkResult res = vkAllocateMemory(pool->logical_device, &alloc_info, pool->allocator, &block.address.handle); + block.address.type = memory_type; + block.address.size = new_pool_size; + + RendVkPoolMemory *mem_pool = &pool->mem_pools[memory_type]; + p_darray_push(mem_pool->block_darr, block); + + RendVkPoolLayout layout = { + .offset = 0, + .size = new_pool_size + }; + p_darray_push(mem_pool->block_darr[pool_size - 1].layout_darr, layout); + + pool->total_allocations++; + + size_t pool_size = p_darray_len(mem_pool->block_darr); + return pool_size - 1; +} + +static RendVkMemory +rend_vk_pool_alloc(RendVkPoolAllocator *pool, VkDeviceSize size, uint32_t usage, uint32_t memory_type, VkMemoryPropertyFlags properties) +{ + RendVkPoolMemory *mem_pool = &pool->mem_pools[memory_type]; + + VkDeviceSize requested_alloc_size = ((size / pool->page_size) + 1) * pool->page_size; + pool->mem_type_alloc_sizes[memory_type] += requested_alloc_size; + + /* find free chunk for allocation + * TODO: free list? + */ + int whole_page = usage != VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + uint64_t block_idx = UINT64_MAX; + uint64_t span_idx = UINT64_MAX; + for (uint32_t i = 0; i < p_darray_len(mem_pool->block_darr); ++i) { + for (uint32_t j = 0; j < p_darray_len(mem_pool->block_darr[i].layout_darr); ++j) { + int valid = (whole_page) ? mem_pool->block_darr[i].layout_darr[j].offset == 0 : 1; + if (mem_pool->block_darr[i].layout_darr[j].size >= size && valid) { + block_idx = i; + span_idx = j; + } + } + } + + if (block_idx == UINT64_MAX || span_idx == UINT64_MAX) { + block_idx = rend_vk_pool_add_block(pool, size, memory_type, properites, whole_page); + span_idx = 0; + } + + mem_pool->block_darr[block_idx].reserved = whole_page; + + RendVkMemory memory = { + .device_memory = mem_pool->block_darr[block_idx].address.handle, + .size = size, + .offset = mem_pool->block_darr[block_idx].layout_darr[span_idx].offset, + .type = memory_type, + + .logical_device = pool->logical_device, + .allocator = pool->allocator, + .host_mapped_memory = 0, + .physical_device = pool->physical_device, + .properties = properties, + + .id = block_idx, + }; + + /* mark chunck */ + mem_pool->block_darr[block_idx].layout_darr[span_idx].offset += size; + mem_pool->block_darr[block_idx].layout_darr[span_idx].size -= size; + return memory; +} + + +static void +rend_vk_pool_free(RendVkPoolAllocator *pool, RendVkMemory *memory) +{ + VkDeviceSize requested = ((memory->size / pool->page_size) + 1) * pool->page_size; + + RendVkPoolMemory *mem_pool = &pool->mem_pools[memory->type]; + + pool->blocks_[allocation.id].pageReserved = false; + + mem_pool->block_darr[block_idx].layout_darr[span_idx].offset += size; + mem_pool->block_darr[block_idx].layout_darr[span_idx].size -= size; + + OffsetSize span = {allocation.offset, requestedAllocSize }; + bool found = false; + + uint32_t numLayoutMems = pool.blocks[allocation.id].layout.size(); + for (uint32_t j = 0; j < numLayoutMems; ++j) + { + if (pool.blocks[allocation.id].layout[j].offset == requestedAllocSize +allocation.offset) + { + pool.blocks[allocation.id].layout[j].offset = allocation.offset; + pool.blocks[allocation.id].layout[j].size += requestedAllocSize; + found = true; + break; + } + } + + if (!found) + { + state.memPools[allocation.type].blocks[allocation.id].layout.push_back(span); + state.memTypeAllocSizes[allocation.type] -= requestedAllocSize; + } +} + +static void +rend_vk_pool_destroy(RendVkPoolAllocator *pool) +{ + if (pool->mem_pools) { + for (size_t u = 0; u < pool->memory_type_count; ++u) { + RendVkPoolMemory mem_pool = pool->mem_pools[u]; + for (size_t b = 0; b < p_darray_len(mem_pool.block_darr); ++b) { + RendVKPoolBlock block = mem_pool.block_darr[b]; + p_darray_destroy(block.layout_darr); + } + p_darray_destroy(mem_pool.block_darr); + } + free(pool->mem_pools); + pool->mem_pools = 0; + } + if (pool->mem_type_alloc_sizes) { + free(pool->mem_type_alloc_sizes); + pool->mem_type_alloc_sizes = 0; + } + memset(pool, 0, sizeof *pool); +} +#endif diff --git a/unused code/rend_vk_sbta.c b/unused code/rend_vk_sbta.c new file mode 100644 index 0000000..7bc51c4 --- /dev/null +++ b/unused code/rend_vk_sbta.c @@ -0,0 +1,331 @@ +#pragma once +#include "rend_internal.h" +#include "rend_vk_internal.h" +#include <vulkan/vulkan_core.h> + +/* + * Sparse Bindless Texture Array (SBTA) + * + */ + +struct RendVkSbta { + VkDevice logical_device; + RendVkImage image; /* single 2D array image, arrayLayers = max_layers */ + VkImageView *views; /* per-layer VkImageView array */ + VkFormat format; + VkExtent2D extent; + uint32_t max_layers; + uint32_t mip_levels; + uint64_t *bitmap; /* 1 bit per layer slot */ + uint32_t bitmap_word_count; + uint32_t allocated_count; + RendMemory memory; +}; + +static inline uint32_t +rend_vk_sbta_mip_count(uint32_t w, uint32_t h) +{ + uint32_t v = (w > h) ? w : h; + uint32_t levels = 1; + while (v >>= 1) { levels++; } + return levels; +} + +static void +rend_vk_sbta_create(RendVkSbta *sbta, VkDevice logical_device, VkExtent2D extent, uint32_t layers) +{ + memset(sbta, 0, sizeof *sbta); + sbta->logical_device = logical_device; + sbta->format = VK_FORMAT_R8G8B8A8_SRGB; + sbta->extent = extent; + sbta->max_layers = layers; + sbta->mip_levels = rend_vk_sbta_mip_count(extent.width, extent.height); + + /* create image */ + sbta->image = rend_vk_image_create( + logical_device, + VK_IMAGE_TYPE_2D, + extent.width, extent.height, + sbta->format, + VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + 1, /* depth */ + sbta->mip_levels, + layers, + VK_SAMPLE_COUNT_1_BIT, + VK_SHARING_MODE_EXCLUSIVE + ); + + /* allocate per-layer views array */ + sbta->views = rmalloc(layers * sizeof *sbta->views); + memset(sbta->views, 0, layers * sizeof *sbta->views); + + /* bitmap: ceil(layers / 64) words */ + sbta->bitmap_word_count = (layers + 63) / 64; + sbta->bitmap = rmalloc(sbta->bitmap_word_count * sizeof *sbta->bitmap); + memset(sbta->bitmap, 0, sbta->bitmap_word_count * sizeof *sbta->bitmap); + sbta->allocated_count = 0; +} + +static void +rend_vk_sbta_create_views(RendVkSbta *sbta) +{ + for (uint32_t i = 0; i < sbta->max_layers; ++i) { + VkImageViewCreateInfo view_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = sbta->image.handle, + .format = sbta->format, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .subresourceRange.baseMipLevel = 0, + .subresourceRange.levelCount = sbta->mip_levels, + .subresourceRange.baseArrayLayer = i, + .subresourceRange.layerCount = 1, + }; + vkCreateImageView(sbta->logical_device, &view_info, vk_allocator, &sbta->views[i]); + } +} + +static void +rend_vk_sbta_destroy(RendVkSbta *sbta) +{ + for (uint32_t i = 0; i < sbta->max_layers; ++i) { + if (sbta->views[i]) { + vkDestroyImageView(sbta->logical_device, sbta->views[i], vk_allocator); + } + } + + rend_vk_image_destroy(&sbta->image); + + rfree(sbta->views); + rfree(sbta->bitmap); + memset(sbta, 0, sizeof *sbta); +} + +static uint64_t +rend_vk_sbta_alloc(RendVkSbta *sbta) +{ + for (uint32_t w = 0; w < sbta->bitmap_word_count; ++w) { + if (sbta->bitmap[w] == ~(uint64_t)0) continue; /* word full */ + + /* find first zero bit */ + uint64_t word = sbta->bitmap[w]; + uint64_t bit = ~word & (word + 1); /* isolate lowest zero bit */ + uint32_t bit_index = 0; + uint64_t tmp = bit; + while (tmp >>= 1) { bit_index++; } + + uint64_t slot = (uint64_t)w * 64 + bit_index; + if (slot >= sbta->max_layers) return UINT64_MAX; /* past capacity */ + + sbta->bitmap[w] |= bit; + sbta->allocated_count++; + return slot; + } + return UINT64_MAX; /* full */ +} + +static void +rend_vk_sbta_free(RendVkSbta *sbta, uint64_t idx) +{ + assert(idx < sbta->max_layers && "SBTA free: index out of range"); + + uint32_t word = (uint32_t)(idx / 64); + uint32_t bit = (uint32_t)(idx % 64); + assert((sbta->bitmap[word] & (1ULL << bit)) && "SBTA free: slot not allocated (double free?)"); + + sbta->bitmap[word] &= ~(1ULL << bit); + sbta->allocated_count--; +} + +static void +rend_vk_sbta_upload(RendVkSbta *sbta, VkCommandBuffer cmd, RendVkArenaAllocator *staging_arena, uint64_t slot, void *pixels, uint64_t size) +{ + assert(slot < sbta->max_layers); + assert(pixels && size > 0); + + /* ---- staging buffer ---- */ + VkBufferCreateInfo buf_info = { + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .size = size, + .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + }; + + VkBuffer staging_buf; + vkCreateBuffer(sbta->logical_device, &buf_info, vk_allocator, &staging_buf); + + VkMemoryRequirements staging_reqs; + vkGetBufferMemoryRequirements(sbta->logical_device, staging_buf, &staging_reqs); + + uint32_t host_index = vk_device.host_index; + RendMemory staging_mem = rend_vk_arena_alloc(staging_arena, staging_reqs.size, host_index); + vkBindBufferMemory(sbta->logical_device, staging_buf, (VkDeviceMemory) staging_mem.device_memory, staging_mem.offset); + + /* copy pixels into staging */ + memcpy(staging_mem.host_mapped_memory, pixels, size); + + /* ---- transition layer mip 0: UNDEFINED -> TRANSFER_DST ---- */ + VkImageMemoryBarrier2 barrier_to_dst = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, + .srcStageMask = VK_PIPELINE_STAGE_2_NONE, + .srcAccessMask = VK_ACCESS_2_NONE, + .dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT, + .dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .image = sbta->image.handle, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = sbta->mip_levels, + .baseArrayLayer = (uint32_t)slot, + .layerCount = 1, + }, + }; + + VkDependencyInfo dep_to_dst = { + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier_to_dst, + }; + vkCmdPipelineBarrier2(cmd, &dep_to_dst); + + /* ---- copy staging -> image mip 0 ---- */ + VkBufferImageCopy copy_region = { + .bufferOffset = 0, + .imageSubresource = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = (uint32_t)slot, + .layerCount = 1, + }, + .imageExtent = { sbta->extent.width, sbta->extent.height, 1 }, + }; + + vkCmdCopyBufferToImage(cmd, staging_buf, sbta->image.handle, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©_region); + + /* ---- generate mipmaps via blit chain ---- */ + int32_t mip_w = (int32_t)sbta->extent.width; + int32_t mip_h = (int32_t)sbta->extent.height; + + for (uint32_t mip = 1; mip < sbta->mip_levels; ++mip) { + /* transition previous mip: TRANSFER_DST -> TRANSFER_SRC */ + VkImageMemoryBarrier2 barrier_src = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, + .srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT, + .srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, + .dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT, + .dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .image = sbta->image.handle, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = mip - 1, + .levelCount = 1, + .baseArrayLayer = (uint32_t)slot, + .layerCount = 1, + }, + }; + + VkDependencyInfo dep_src = { + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier_src, + }; + vkCmdPipelineBarrier2(cmd, &dep_src); + + /* blit from mip-1 to mip */ + int32_t next_w = (mip_w > 1) ? mip_w / 2 : 1; + int32_t next_h = (mip_h > 1) ? mip_h / 2 : 1; + + VkImageBlit2 blit = { + .sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2, + .srcSubresource = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = mip - 1, + .baseArrayLayer = (uint32_t)slot, + .layerCount = 1, + }, + .srcOffsets = { {0, 0, 0}, {mip_w, mip_h, 1} }, + .dstSubresource = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = mip, + .baseArrayLayer = (uint32_t)slot, + .layerCount = 1, + }, + .dstOffsets = { {0, 0, 0}, {next_w, next_h, 1} }, + }; + + VkBlitImageInfo2 blit_info = { + .sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2, + .srcImage = sbta->image.handle, + .srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .dstImage = sbta->image.handle, + .dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .regionCount = 1, + .pRegions = &blit, + .filter = VK_FILTER_LINEAR, + }; + vkCmdBlitImage2(cmd, &blit_info); + + mip_w = next_w; + mip_h = next_h; + } + + /* ---- final transition: all mips -> SHADER_READ_ONLY ---- */ + /* last mip is still TRANSFER_DST, all others are TRANSFER_SRC */ + + /* transition last mip: TRANSFER_DST -> SHADER_READ_ONLY */ + VkImageMemoryBarrier2 barriers_final[2] = { + /* mips 0..N-2: TRANSFER_SRC -> SHADER_READ_ONLY */ + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, + .srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT, + .srcAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT, + .dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, + .dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .image = sbta->image.handle, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = (sbta->mip_levels > 1) ? sbta->mip_levels - 1 : 1, + .baseArrayLayer = (uint32_t)slot, + .layerCount = 1, + }, + }, + /* last mip: TRANSFER_DST -> SHADER_READ_ONLY */ + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, + .srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT, + .srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, + .dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, + .dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .image = sbta->image.handle, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = sbta->mip_levels - 1, + .levelCount = 1, + .baseArrayLayer = (uint32_t)slot, + .layerCount = 1, + }, + }, + }; + + uint32_t barrier_count = (sbta->mip_levels > 1) ? 2 : 1; + + /* if only 1 mip, use second barrier (TRANSFER_DST path) */ + VkImageMemoryBarrier2 *barrier_ptr = (sbta->mip_levels > 1) ? barriers_final : &barriers_final[1]; + + VkDependencyInfo dep_final = { + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .imageMemoryBarrierCount = barrier_count, + .pImageMemoryBarriers = barrier_ptr, + }; + vkCmdPipelineBarrier2(cmd, &dep_final); +} |
