/* RetroArch - A frontend for libretro. * Copyright (C) 2016 - Hans-Kristian Arntzen * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with RetroArch. * If not, see . */ #include "vulkan_common.h" #include uint32_t vulkan_find_memory_type(const VkPhysicalDeviceMemoryProperties *mem_props, uint32_t device_reqs, uint32_t host_reqs) { uint32_t i; for (i = 0; i < VK_MAX_MEMORY_TYPES; i++) { if ((device_reqs & (1u << i)) && (mem_props->memoryTypes[i].propertyFlags & host_reqs) == host_reqs) return i; } RARCH_ERR("[Vulkan]: Failed to find valid memory type. This should never happen."); abort(); } uint32_t vulkan_find_memory_type_fallback(const VkPhysicalDeviceMemoryProperties *mem_props, uint32_t device_reqs, uint32_t host_reqs) { uint32_t i; for (i = 0; i < VK_MAX_MEMORY_TYPES; i++) { if ((device_reqs & (1u << i)) && (mem_props->memoryTypes[i].propertyFlags & host_reqs) == host_reqs) return i; } return vulkan_find_memory_type(mem_props, device_reqs, 0); } void vulkan_map_persistent_texture(VkDevice device, struct vk_texture *texture) { vkMapMemory(device, texture->memory, texture->offset, texture->size, 0, &texture->mapped); } #ifdef VULKAN_DEBUG_TEXTURE_ALLOC static VkImage vk_images[4 * 1024]; static unsigned vk_count; void vulkan_log_textures(void) { unsigned i; for (i = 0; i < vk_count; i++) { RARCH_WARN("[Vulkan]: Found leaked texture %llu.\n", (unsigned long long)vk_images[i]); } vk_count = 0; } static unsigned track_seq; static void vulkan_track_alloc(VkImage image) { vk_images[vk_count++] = image; RARCH_LOG("[Vulkan]: Alloc %llu (%u).\n", (unsigned long long)image, track_seq); track_seq++; } static void vulkan_track_dealloc(VkImage image) { unsigned i; for (i = 0; i < vk_count; i++) { if (image == vk_images[i]) { vk_count--; memmove(vk_images + i, vk_images + 1 + i, sizeof(VkImage) * (vk_count - i)); return; } } retro_assert(0 && "Couldn't find VkImage in dealloc!"); } #endif struct vk_texture vulkan_create_texture(vk_t *vk, struct vk_texture *old, unsigned width, unsigned height, VkFormat format, const void *initial, const VkComponentMapping *swizzle, enum vk_texture_type type) { /* TODO: Evaluate how we should do texture uploads on discrete cards optimally. * For integrated GPUs, using linear texture is highly desirable to avoid extra copies, but * we might need to take a DMA transfer with block interleave on desktop GPUs. * * Also, Vulkan drivers are not required to support sampling from linear textures * (only TRANSFER), but seems to work fine on GPUs I've tested so far. */ VkDevice device = vk->context->device; struct vk_texture tex; VkImageCreateInfo info = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; VkImageViewCreateInfo view = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; VkMemoryAllocateInfo alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; VkImageSubresource subresource = { VK_IMAGE_ASPECT_COLOR_BIT }; VkMemoryRequirements mem_reqs; VkSubresourceLayout layout; if (type == VULKAN_TEXTURE_STATIC && !initial) retro_assert(0 && "Static textures must have initial data.\n"); memset(&tex, 0, sizeof(tex)); info.imageType = VK_IMAGE_TYPE_2D; info.format = format; info.extent.width = width; info.extent.height = height; info.extent.depth = 1; info.mipLevels = 1; info.arrayLayers = 1; info.samples = VK_SAMPLE_COUNT_1_BIT; info.tiling = type != VULKAN_TEXTURE_STATIC ? VK_IMAGE_TILING_LINEAR : VK_IMAGE_TILING_OPTIMAL; info.usage = VK_IMAGE_USAGE_SAMPLED_BIT; if (type == VULKAN_TEXTURE_STATIC) info.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; if (type == VULKAN_TEXTURE_READBACK) info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; /* We'll transition this on first use for streamed textures. */ info.initialLayout = type == VULKAN_TEXTURE_STREAMED ? VK_IMAGE_LAYOUT_PREINITIALIZED : VK_IMAGE_LAYOUT_UNDEFINED; vkCreateImage(device, &info, NULL, &tex.image); #if 0 vulkan_track_alloc(tex.image); #endif vkGetImageMemoryRequirements(device, tex.image, &mem_reqs); alloc.allocationSize = mem_reqs.size; if (type == VULKAN_TEXTURE_STATIC) { alloc.memoryTypeIndex = vulkan_find_memory_type_fallback(&vk->context->memory_properties, mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); } else { /* This must exist. */ alloc.memoryTypeIndex = vulkan_find_memory_type(&vk->context->memory_properties, mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); } /* We're not reusing the objects themselves. */ if (old && old->view != VK_NULL_HANDLE) vkDestroyImageView(vk->context->device, old->view, NULL); if (old && old->image != VK_NULL_HANDLE) { vkDestroyImage(vk->context->device, old->image, NULL); #ifdef VULKAN_DEBUG_TEXTURE_ALLOC vulkan_track_dealloc(old->image); #endif } /* We can pilfer the old memory and move it over to the new texture. */ if (old && old->memory_size >= mem_reqs.size && old->memory_type == alloc.memoryTypeIndex) { tex.memory = old->memory; tex.memory_size = old->memory_size; tex.memory_type = old->memory_type; if (old->mapped) vkUnmapMemory(device, old->memory); old->memory = VK_NULL_HANDLE; } else { vkAllocateMemory(device, &alloc, NULL, &tex.memory); tex.memory_size = alloc.allocationSize; tex.memory_type = alloc.memoryTypeIndex; } if (old) { if (old->memory != VK_NULL_HANDLE) vkFreeMemory(device, old->memory, NULL); memset(old, 0, sizeof(*old)); } vkBindImageMemory(device, tex.image, tex.memory, 0); view.image = tex.image; view.viewType = VK_IMAGE_VIEW_TYPE_2D; view.format = format; if (swizzle) view.components = *swizzle; else { view.components.r = VK_COMPONENT_SWIZZLE_R; view.components.g = VK_COMPONENT_SWIZZLE_G; view.components.b = VK_COMPONENT_SWIZZLE_B; view.components.a = VK_COMPONENT_SWIZZLE_A; } view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; view.subresourceRange.levelCount = 1; view.subresourceRange.layerCount = 1; vkCreateImageView(device, &view, NULL, &tex.view); vkGetImageSubresourceLayout(device, tex.image, &subresource, &layout); tex.stride = layout.rowPitch; tex.offset = layout.offset; tex.size = layout.size; tex.layout = info.initialLayout; tex.width = width; tex.height = height; tex.format = format; if (initial && type == VULKAN_TEXTURE_STREAMED) { unsigned bpp = vulkan_format_to_bpp(tex.format); unsigned stride = tex.width * bpp; unsigned x, y; uint8_t *dst; const uint8_t *src; void *ptr; vkMapMemory(device, tex.memory, tex.offset, tex.size, 0, &ptr); dst = (uint8_t*)ptr; src = (const uint8_t*)initial; for (y = 0; y < tex.height; y++, dst += tex.stride, src += stride) memcpy(dst, src, width * bpp); vkUnmapMemory(device, tex.memory); } else if (initial && type == VULKAN_TEXTURE_STATIC) { VkCommandBufferAllocateInfo info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; VkCommandBufferBeginInfo begin_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; VkSubmitInfo submit_info = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; VkImageCopy region; VkCommandBuffer staging; unsigned bpp = vulkan_format_to_bpp(tex.format); struct vk_texture tmp = vulkan_create_texture(vk, NULL, width, height, format, initial, NULL, VULKAN_TEXTURE_STREAMED); info.commandPool = vk->staging_pool; info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; info.commandBufferCount = 1; vkAllocateCommandBuffers(vk->context->device, &info, &staging); begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(staging, &begin_info); vulkan_image_layout_transition(vk, staging, tmp.image, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_GENERAL, VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); vulkan_image_layout_transition(vk, staging, tex.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); memset(®ion, 0, sizeof(region)); region.extent.width = width; region.extent.height = height; region.extent.depth = 1; region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.srcSubresource.layerCount = 1; region.dstSubresource = region.srcSubresource; vkCmdCopyImage(staging, tmp.image, VK_IMAGE_LAYOUT_GENERAL, tex.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); vulkan_image_layout_transition(vk, staging, tex.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); vkEndCommandBuffer(staging); submit_info.commandBufferCount = 1; submit_info.pCommandBuffers = &staging; slock_lock(vk->context->queue_lock); vkQueueSubmit(vk->context->queue, 1, &submit_info, VK_NULL_HANDLE); /* TODO: Very crude, but texture uploads only happen during init, * so waiting for GPU to complete transfer and blocking isn't a big deal. */ vkQueueWaitIdle(vk->context->queue); slock_unlock(vk->context->queue_lock); vkFreeCommandBuffers(vk->context->device, vk->staging_pool, 1, &staging); vulkan_destroy_texture(vk->context->device, &tmp); tex.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } return tex; } void vulkan_destroy_texture(VkDevice device, struct vk_texture *tex) { if (tex->mapped) vkUnmapMemory(device, tex->memory); vkFreeMemory(device, tex->memory, NULL); vkDestroyImageView(device, tex->view, NULL); vkDestroyImage(device, tex->image, NULL); #ifdef VULKAN_DEBUG_TEXTURE_ALLOC vulkan_track_dealloc(tex->image); #endif memset(tex, 0, sizeof(*tex)); } static void vulkan_write_quad_descriptors(VkDevice device, VkDescriptorSet set, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range, const struct vk_texture *texture, VkSampler sampler) { VkWriteDescriptorSet write = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; VkDescriptorImageInfo image_info; VkDescriptorBufferInfo buffer_info; image_info.sampler = sampler; image_info.imageView = texture->view; image_info.imageLayout = texture->layout; buffer_info.buffer = buffer; buffer_info.offset = offset; buffer_info.range = range; write.dstSet = set; write.dstBinding = 0; write.descriptorCount = 1; write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; write.pBufferInfo = &buffer_info; vkUpdateDescriptorSets(device, 1, &write, 0, NULL); write.dstSet = set; write.dstBinding = 1; write.descriptorCount = 1; write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; write.pImageInfo = &image_info; vkUpdateDescriptorSets(device, 1, &write, 0, NULL); } void vulkan_transition_texture(vk_t *vk, struct vk_texture *texture) { /* Transition to GENERAL layout for linear streamed textures. * We're using linear textures here, so only GENERAL layout is supported. */ if (texture->layout == VK_IMAGE_LAYOUT_PREINITIALIZED) { vulkan_image_layout_transition(vk, vk->cmd, texture->image, texture->layout, VK_IMAGE_LAYOUT_GENERAL, VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); texture->layout = VK_IMAGE_LAYOUT_GENERAL; } } static void vulkan_check_dynamic_state(vk_t *vk) { if (vk->tracker.dirty & VULKAN_DIRTY_DYNAMIC_BIT) { const VkRect2D sci = {{ vk->vp.x, vk->vp.y }, { vk->vp.width, vk->vp.height }}; vkCmdSetViewport(vk->cmd, 0, 1, &vk->vk_vp); vkCmdSetScissor(vk->cmd, 0, 1, &sci); vk->tracker.dirty &= ~VULKAN_DIRTY_DYNAMIC_BIT; } } void vulkan_draw_triangles(vk_t *vk, const struct vk_draw_triangles *call) { vulkan_transition_texture(vk, call->texture); if (call->pipeline != vk->tracker.pipeline) { vkCmdBindPipeline(vk->cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, call->pipeline); vk->tracker.pipeline = call->pipeline; /* Changing pipeline invalidates dynamic state. */ vk->tracker.dirty |= VULKAN_DIRTY_DYNAMIC_BIT; } vulkan_check_dynamic_state(vk); /* Upload descriptors */ { VkDescriptorSet set; if (memcmp(call->mvp, &vk->tracker.mvp, sizeof(*call->mvp)) || call->texture->view != vk->tracker.view || call->sampler != vk->tracker.sampler) { /* Upload UBO */ struct vk_buffer_range range; if (!vulkan_buffer_chain_alloc(vk->context, &vk->chain->ubo, sizeof(*call->mvp), &range)) return; memcpy(range.data, call->mvp, sizeof(*call->mvp)); set = vulkan_descriptor_manager_alloc(vk->context->device, &vk->chain->descriptor_manager); vulkan_write_quad_descriptors(vk->context->device, set, range.buffer, range.offset, sizeof(*call->mvp), call->texture, call->sampler); vkCmdBindDescriptorSets(vk->cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, vk->pipelines.layout, 0, 1, &set, 0, NULL); vk->tracker.view = call->texture->view; vk->tracker.sampler = call->sampler; vk->tracker.mvp = *call->mvp; } } /* VBO is already uploaded. */ vkCmdBindVertexBuffers(vk->cmd, 0, 1, &call->vbo->buffer, &call->vbo->offset); /* Draw the quad */ vkCmdDraw(vk->cmd, call->vertices, 1, 0, 0); } void vulkan_draw_quad(vk_t *vk, const struct vk_draw_quad *quad) { vulkan_transition_texture(vk, quad->texture); if (quad->pipeline != vk->tracker.pipeline) { vkCmdBindPipeline(vk->cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, quad->pipeline); vk->tracker.pipeline = quad->pipeline; /* Changing pipeline invalidates dynamic state. */ vk->tracker.dirty |= VULKAN_DIRTY_DYNAMIC_BIT; } vulkan_check_dynamic_state(vk); /* Upload descriptors */ { VkDescriptorSet set; struct vk_buffer_range range; if (!vulkan_buffer_chain_alloc(vk->context, &vk->chain->ubo, sizeof(*quad->mvp), &range)) return; if (memcmp(quad->mvp, &vk->tracker.mvp, sizeof(*quad->mvp)) || quad->texture->view != vk->tracker.view || quad->sampler != vk->tracker.sampler) { /* Upload UBO */ struct vk_buffer_range range; if (!vulkan_buffer_chain_alloc(vk->context, &vk->chain->ubo, sizeof(*quad->mvp), &range)) return; memcpy(range.data, quad->mvp, sizeof(*quad->mvp)); set = vulkan_descriptor_manager_alloc(vk->context->device, &vk->chain->descriptor_manager); vulkan_write_quad_descriptors(vk->context->device, set, range.buffer, range.offset, sizeof(*quad->mvp), quad->texture, quad->sampler); vkCmdBindDescriptorSets(vk->cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, vk->pipelines.layout, 0, 1, &set, 0, NULL); vk->tracker.view = quad->texture->view; vk->tracker.sampler = quad->sampler; vk->tracker.mvp = *quad->mvp; } } /* Upload VBO */ { struct vk_buffer_range range; if (!vulkan_buffer_chain_alloc(vk->context, &vk->chain->vbo, 6 * sizeof(struct vk_vertex), &range)) return; vulkan_write_quad_vbo((struct vk_vertex*)range.data, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, &quad->color); vkCmdBindVertexBuffers(vk->cmd, 0, 1, &range.buffer, &range.offset); } /* Draw the quad */ vkCmdDraw(vk->cmd, 6, 1, 0, 0); } void vulkan_image_layout_transition(vk_t *vk, VkCommandBuffer cmd, VkImage image, VkImageLayout old_layout, VkImageLayout new_layout, VkAccessFlags srcAccess, VkAccessFlags dstAccess, VkPipelineStageFlags srcStages, VkPipelineStageFlags dstStages) { VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; barrier.srcAccessMask = srcAccess; barrier.dstAccessMask = dstAccess; barrier.oldLayout = old_layout; barrier.newLayout = new_layout; barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.image = image; barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; barrier.subresourceRange.levelCount = 1; barrier.subresourceRange.layerCount = 1; vkCmdPipelineBarrier(cmd, srcStages, dstStages, 0, 0, NULL, 0, NULL, 1, &barrier); } struct vk_buffer vulkan_create_buffer(const struct vulkan_context *context, size_t size, VkBufferUsageFlags usage) { struct vk_buffer buffer; VkMemoryRequirements mem_reqs; VkMemoryAllocateInfo alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; VkBufferCreateInfo info = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; info.size = size; info.usage = usage; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; vkCreateBuffer(context->device, &info, NULL, &buffer.buffer); vkGetBufferMemoryRequirements(context->device, buffer.buffer, &mem_reqs); alloc.allocationSize = mem_reqs.size; alloc.memoryTypeIndex = vulkan_find_memory_type(&context->memory_properties, mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); vkAllocateMemory(context->device, &alloc, NULL, &buffer.memory); vkBindBufferMemory(context->device, buffer.buffer, buffer.memory, 0); buffer.size = alloc.allocationSize; vkMapMemory(context->device, buffer.memory, 0, buffer.size, 0, &buffer.mapped); return buffer; } void vulkan_destroy_buffer(VkDevice device, struct vk_buffer *buffer) { vkUnmapMemory(device, buffer->memory); vkFreeMemory(device, buffer->memory, NULL); vkDestroyBuffer(device, buffer->buffer, NULL); memset(buffer, 0, sizeof(*buffer)); } static struct vk_descriptor_pool *vulkan_alloc_descriptor_pool(VkDevice device, const struct vk_descriptor_manager *manager) { unsigned i; VkDescriptorPoolCreateInfo pool_info = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; VkDescriptorSetAllocateInfo alloc_info = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO }; struct vk_descriptor_pool *pool = (struct vk_descriptor_pool*)calloc(1, sizeof(*pool)); if (!pool) return NULL; pool_info.maxSets = VULKAN_DESCRIPTOR_MANAGER_BLOCK_SETS; pool_info.poolSizeCount = manager->num_sizes; pool_info.pPoolSizes = manager->sizes; pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; vkCreateDescriptorPool(device, &pool_info, NULL, &pool->pool); /* Just allocate all descriptor sets up front. */ alloc_info.descriptorPool = pool->pool; alloc_info.descriptorSetCount = 1; alloc_info.pSetLayouts = &manager->set_layout; for (i = 0; i < VULKAN_DESCRIPTOR_MANAGER_BLOCK_SETS; i++) vkAllocateDescriptorSets(device, &alloc_info, &pool->sets[i]); return pool; } VkDescriptorSet vulkan_descriptor_manager_alloc(VkDevice device, struct vk_descriptor_manager *manager) { if (manager->count < VULKAN_DESCRIPTOR_MANAGER_BLOCK_SETS) return manager->current->sets[manager->count++]; while (manager->current->next) { manager->current = manager->current->next; manager->count = 0; return manager->current->sets[manager->count++]; } manager->current->next = vulkan_alloc_descriptor_pool(device, manager); retro_assert(manager->current->next); manager->current = manager->current->next; manager->count = 0; return manager->current->sets[manager->count++]; } void vulkan_descriptor_manager_restart(struct vk_descriptor_manager *manager) { manager->current = manager->head; manager->count = 0; } struct vk_descriptor_manager vulkan_create_descriptor_manager(VkDevice device, const VkDescriptorPoolSize *sizes, unsigned num_sizes, VkDescriptorSetLayout set_layout) { struct vk_descriptor_manager manager; memset(&manager, 0, sizeof(manager)); retro_assert(num_sizes <= VULKAN_MAX_DESCRIPTOR_POOL_SIZES); memcpy(manager.sizes, sizes, num_sizes * sizeof(*sizes)); manager.num_sizes = num_sizes; manager.set_layout = set_layout; manager.head = vulkan_alloc_descriptor_pool(device, &manager); retro_assert(manager.head); return manager; } void vulkan_destroy_descriptor_manager(VkDevice device, struct vk_descriptor_manager *manager) { struct vk_descriptor_pool *node = manager->head; while (node) { struct vk_descriptor_pool *next = node->next; vkFreeDescriptorSets(device, node->pool, VULKAN_DESCRIPTOR_MANAGER_BLOCK_SETS, node->sets); vkDestroyDescriptorPool(device, node->pool, NULL); free(node); node = next; } memset(manager, 0, sizeof(*manager)); } static void vulkan_buffer_chain_step(struct vk_buffer_chain *chain) { chain->current = chain->current->next; chain->offset = 0; } static bool vulkan_buffer_chain_suballoc(struct vk_buffer_chain *chain, size_t size, struct vk_buffer_range *range) { VkDeviceSize next_offset = chain->offset + size; if (next_offset <= chain->current->buffer.size) { range->data = (uint8_t*)chain->current->buffer.mapped + chain->offset; range->buffer = chain->current->buffer.buffer; range->offset = chain->offset; chain->offset = (next_offset + chain->alignment - 1) & ~(chain->alignment - 1); return true; } else return false; } static struct vk_buffer_node *vulkan_buffer_chain_alloc_node( const struct vulkan_context *context, size_t size, VkBufferUsageFlags usage) { struct vk_buffer_node *node = (struct vk_buffer_node*)calloc(1, sizeof(*node)); if (!node) return NULL; node->buffer = vulkan_create_buffer(context, size, usage); return node; } struct vk_buffer_chain vulkan_buffer_chain_init(VkDeviceSize block_size, VkDeviceSize alignment, VkBufferUsageFlags usage) { struct vk_buffer_chain chain = { block_size, alignment, 0, usage, NULL, NULL }; return chain; } void vulkan_buffer_chain_discard(struct vk_buffer_chain *chain) { chain->current = chain->head; chain->offset = 0; } bool vulkan_buffer_chain_alloc(const struct vulkan_context *context, struct vk_buffer_chain *chain, size_t size, struct vk_buffer_range *range) { if (!chain->head) { chain->head = vulkan_buffer_chain_alloc_node(context, chain->block_size, chain->usage); if (!chain->head) return false; chain->current = chain->head; chain->offset = 0; } if (vulkan_buffer_chain_suballoc(chain, size, range)) return true; /* We've exhausted the current chain, traverse list until we * can find a block we can use. Usually, we just step once. */ while (chain->current->next) { vulkan_buffer_chain_step(chain); if (vulkan_buffer_chain_suballoc(chain, size, range)) return true; } /* We have to allocate a new node, might allocate larger * buffer here than block_size in case we have a very large allocation. */ if (size < chain->block_size) size = chain->block_size; chain->current->next = vulkan_buffer_chain_alloc_node( context, size, chain->usage); if (!chain->current->next) return false; vulkan_buffer_chain_step(chain); /* This cannot possibly fail. */ retro_assert(vulkan_buffer_chain_suballoc(chain, size, range)); return true; } void vulkan_buffer_chain_free(VkDevice device, struct vk_buffer_chain *chain) { struct vk_buffer_node *node = chain->head; while (node) { struct vk_buffer_node *next = node->next; vulkan_destroy_buffer(device, &node->buffer); free(node); node = next; } memset(chain, 0, sizeof(*chain)); }