/* This file is part of Flycast. Flycast 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 Foundation, either version 2 of the License, or (at your option) any later version. Flycast 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 Flycast. If not, see . */ #include "heap.h" #include "vulkan_context.h" static const VkMemoryPropertyFlags HOST_MEMORY = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; void VulkanHeap::Init(vk::PhysicalDevice physicalDevice, vk::Device device) { verify(!ready); const vk::PhysicalDeviceMemoryProperties props = physicalDevice.getMemoryProperties(); /* One block is 64 MB, or an eighth of the device's memory where that * is less. */ VkDeviceSize largest = 0; for (u32 i = 0; i < props.memoryHeapCount; i++) if ((VkDeviceSize)props.memoryHeaps[i].size > largest) largest = props.memoryHeaps[i].size; VkDeviceSize blockSize = 64u * 1024u * 1024u; if (largest / 8 < blockSize) blockSize = std::max((largest / 8) & ~(VkDeviceSize)0xFFFFF, 4u * 1024u * 1024u); vk_heap_fns_t fns; fns.allocate_memory = vkAllocateMemory; fns.free_memory = vkFreeMemory; fns.map_memory = vkMapMemory; fns.unmap_memory = vkUnmapMemory; if (!vk_heap_init(&heap, (VkDevice)device, (const VkPhysicalDeviceMemoryProperties *)&props, &fns, blockSize, 0)) throwResultException(vk::Result::eErrorInitializationFailed, "vk_heap_init failed"); this->device = device; ready = true; trimIn = 0; /* The first block for textures and render targets and the first one * for the buffers the CPU fills are taken now, so that a frame gets * its memory as an offset and not from the driver. */ vk_heap_reserve(&heap, ~0u, 0, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 0, 1); vk_heap_reserve(&heap, ~0u, HOST_MEMORY, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 1, 1); NOTICE_LOG(RENDERER, "Vulkan heap: %u MB blocks, %u MB reserved", (u32)(blockSize >> 20), (u32)(heap.bytes_reserved >> 20)); } void VulkanHeap::Term() { if (ready) { vk_heap_shutdown(&heap); ready = false; } } /* After the renderer has changed size: blocks that what it had before * left empty go back to the driver, all but the last of each kind. */ void VulkanHeap::TrimSpare() { if (!ready) return; const VkDeviceSize before = heap.bytes_reserved; if (vk_heap_trim_spare(&heap) != 0) NOTICE_LOG(RENDERER, "Vulkan heap: %u MB given back, %u MB reserved", (u32)((before - heap.bytes_reserved) >> 20), (u32)(heap.bytes_reserved >> 20)); } /* Why the heap had nothing to give: what the driver said - no memory of * the kind, or memory it would not map - where there is that to go by. */ vk::Result VulkanHeap::AllocError() const { return heap.last_error != VK_SUCCESS ? (vk::Result)heap.last_error : vk::Result::eErrorOutOfDeviceMemory; } Allocation VulkanHeap::Allocate(const vk::MemoryRequirements& requirements, VkMemoryPropertyFlags required, VkMemoryPropertyFlags preferred, bool linear) { vk_heap_alloc_t alloc; if (!vk_heap_alloc(&heap, (const VkMemoryRequirements *)&requirements, required, preferred, linear, &alloc)) throwResultException(AllocError(), "vk_heap_alloc failed"); return Allocation(&heap, alloc); } Allocation VulkanHeap::AllocateForImage(vk::Image image, bool linear, bool hostVisible, bool transient) { const vk::MemoryRequirements requirements = device.getImageMemoryRequirements(image); vk_heap_alloc_t alloc; if (hostVisible) { if (!vk_heap_alloc(&heap, (const VkMemoryRequirements *)&requirements, HOST_MEMORY, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, linear, &alloc)) throwResultException(AllocError(), "vk_heap_alloc failed"); } /* A transient attachment takes memory the device only backs if it has * to, where there is such a thing. */ else if (!transient || !vk_heap_alloc(&heap, (const VkMemoryRequirements *)&requirements, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT, 0, linear, &alloc)) { if (!vk_heap_alloc(&heap, (const VkMemoryRequirements *)&requirements, 0, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, linear, &alloc)) throwResultException(AllocError(), "vk_heap_alloc failed"); } Allocation allocation(&heap, alloc); device.bindImageMemory(image, vk::DeviceMemory(alloc.memory), alloc.offset); return allocation; } Allocation VulkanHeap::AllocateForBuffer(vk::Buffer buffer, bool hostVisible, bool readBack) { const vk::MemoryRequirements requirements = device.getBufferMemoryRequirements(buffer); VkMemoryPropertyFlags required = 0; VkMemoryPropertyFlags preferred = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; if (hostVisible) { required = HOST_MEMORY; /* What the CPU reads back wants the host's cache between it and * the memory; device memory seen through the bus is write-combined * and reading it is slow. What the CPU only writes goes as close * to the device as there is. */ if (readBack) preferred = VK_MEMORY_PROPERTY_HOST_CACHED_BIT; } Allocation allocation = Allocate(requirements, required, preferred, true); device.bindBufferMemory(buffer, vk::DeviceMemory(allocation.alloc.memory), allocation.alloc.offset); return allocation; }