kk: Complete VK_EXT_memory_budget

Metal provides device properties for the recommended maximum memory usage and
the current amount of memory used. These can be used to provide an estimate
of heap usage and calculate a budget of memory usage by the application before
performance may degrade.

Reviewed-by: Aitor Camacho <aitor@lunarg.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/41523>
This commit is contained in:
squidbus 2026-05-14 06:22:16 -07:00 committed by Marge Bot
parent 682dc50776
commit 0e8fec8d8d
7 changed files with 103 additions and 62 deletions

View file

@ -660,7 +660,7 @@ Khronos extensions that are not part of any Vulkan version:
VK_EXT_line_rasterization DONE (anv, hasvk, hk, kk, nvk, panvk, pvr, lvp, radv, tu, v3dv, vn)
VK_EXT_load_store_op_none DONE (anv, hk, kk, lvp, nvk, panvk, radv, tu, v3dv, vn)
VK_EXT_map_memory_placed DONE (anv, hk, nvk, panvk, pvr, radv, tu, vn)
VK_EXT_memory_budget DONE (anv, hasvk, lvp, nvk, panvk, radv, tu, v3dv, vn)
VK_EXT_memory_budget DONE (anv, hasvk, kk, lvp, nvk, panvk, radv, tu, v3dv, vn)
VK_EXT_memory_priority DONE (lvp, radv)
VK_EXT_mesh_shader DONE (anv/gfx12.5+, lvp, radv, vn)
VK_EXT_multi_draw DONE (anv, hasvk, hk, kk, lvp, nvk, radv, tu, vn, v3dv)

View file

@ -32,6 +32,8 @@ bool mtl_device_supports_sample_count(mtl_device *dev, uint32_t sample_count);
struct mtl_size mtl_device_max_threads_per_threadgroup(mtl_device *dev);
uint32_t mtl_device_max_threadgroup_memory_length(mtl_device *dev);
uint64_t mtl_device_max_buffer_length(mtl_device *dev);
uint64_t mtl_device_recommended_max_working_set_size(mtl_device *dev);
uint64_t mtl_device_current_allocated_size(mtl_device *dev);
/* Timestamp query */
uint64_t mtl_device_get_gpu_timestamp(mtl_device *dev);

View file

@ -156,6 +156,24 @@ mtl_device_max_buffer_length(mtl_device *dev)
}
}
uint64_t
mtl_device_recommended_max_working_set_size(mtl_device *dev)
{
@autoreleasepool {
id<MTLDevice> device = (id<MTLDevice>)dev;
return device.recommendedMaxWorkingSetSize;
}
}
uint64_t
mtl_device_current_allocated_size(mtl_device *dev)
{
@autoreleasepool {
id<MTLDevice> device = (id<MTLDevice>)dev;
return device.currentAllocatedSize;
}
}
/* Timestamp query */
uint64_t
mtl_device_get_gpu_timestamp(mtl_device *dev)

View file

@ -77,6 +77,18 @@ mtl_device_max_buffer_length(mtl_device *dev)
return 0u;
}
uint64_t
mtl_device_recommended_max_working_set_size(mtl_device *dev)
{
return 0u;
}
uint64_t
mtl_device_current_allocated_size(mtl_device *dev)
{
return 0u;
}
/* Timestamp query */
uint64_t
mtl_device_get_gpu_timestamp(mtl_device *dev)

View file

@ -60,12 +60,10 @@ kk_AllocateMemory(VkDevice device, const VkMemoryAllocateInfo *pAllocateInfo,
const VkAllocationCallbacks *pAllocator, VkDeviceMemory *pMem)
{
VK_FROM_HANDLE(kk_device, dev, device);
struct kk_physical_device *pdev = kk_device_physical(dev);
struct kk_device_memory *mem;
VkResult result = VK_SUCCESS;
const VkImportMemoryMetalHandleInfoEXT *metal_info = vk_find_struct_const(
pAllocateInfo->pNext, IMPORT_MEMORY_METAL_HANDLE_INFO_EXT);
const VkMemoryType *type = &pdev->mem_types[pAllocateInfo->memoryTypeIndex];
// TODO_KOSMICKRISP Do the actual memory allocation with alignment requirements
uint32_t alignment = (1ULL << 12);
@ -102,9 +100,6 @@ kk_AllocateMemory(VkDevice device, const VkMemoryAllocateInfo *pAllocateInfo,
goto fail_alloc;
}
struct kk_memory_heap *heap = &pdev->mem_heaps[type->heapIndex];
p_atomic_add(&heap->used, mem->bo->size_B);
*pMem = kk_device_memory_to_handle(mem);
return VK_SUCCESS;
@ -120,15 +115,10 @@ kk_FreeMemory(VkDevice device, VkDeviceMemory _mem,
{
VK_FROM_HANDLE(kk_device, dev, device);
VK_FROM_HANDLE(kk_device_memory, mem, _mem);
struct kk_physical_device *pdev = kk_device_physical(dev);
if (!mem)
return;
const VkMemoryType *type = &pdev->mem_types[mem->vk.memory_type_index];
struct kk_memory_heap *heap = &pdev->mem_heaps[type->heapIndex];
p_atomic_add(&heap->used, -((int64_t)mem->bo->size_B));
kk_destroy_bo(dev, mem->bo);
vk_device_memory_destroy(&dev->vk, pAllocator, &mem->vk);

View file

@ -149,6 +149,7 @@ kk_get_device_extensions(const struct kk_instance *instance,
.EXT_external_memory_metal = true,
.EXT_image_2d_view_of_3d = true,
.EXT_load_store_op_none = true,
.EXT_memory_budget = true,
.EXT_multi_draw = true,
.EXT_mutable_descriptor_type = true,
.EXT_post_depth_coverage = true,
@ -806,25 +807,80 @@ kk_physical_device_free_disk_cache(struct kk_physical_device *pdev)
static uint64_t
kk_get_sysmem_heap_size(void)
{
/* Report the total amount of system memory as the actual heap size */
uint64_t sysmem_size_B = 0;
if (!os_get_total_physical_memory(&sysmem_size_B))
return 0;
/* Use 3/4 of total size to avoid swapping */
return ROUND_DOWN_TO(sysmem_size_B * 3 / 4, 1 << 20);
return sysmem_size_B;
}
static uint64_t
kk_get_sysmem_heap_available(struct kk_physical_device *pdev)
kk_get_sysmem_heap_budget(struct kk_physical_device *pdev)
{
/* From the Vulkan 1.3.278 spec:
*
* "heapBudget is an array of VK_MAX_MEMORY_HEAPS VkDeviceSize
* values in which memory budgets are returned, with one
* element for each memory heap. A heaps budget is a rough
* estimate of how much memory the process can allocate from
* that heap before allocations may fail or cause performance
* degradation. The budget includes any currently allocated
* device memory."
*
* and
*
* "The heapBudget value must be less than or equal to
* VkMemoryHeap::size for each heap."
*
* From Metal documentation for recommendedMaxWorkingSetSize:
*
* An approximation of how much memory, in bytes, this GPU device can
* allocate without affecting its runtime performance.
*
* From Metal documentation for currentAllocatedSize:
*
* The total amount of memory, in bytes, the GPU device is using for all
* of its resources.
*
* First, determine the total and available system memory to calculate the
* amount of used memory. Then, subtract this from the Metal-defined budget,
* and add back the current used memory by this device.
*/
uint64_t sysmem_size_B = 0;
if (!os_get_available_system_memory(&sysmem_size_B)) {
vk_loge(VK_LOG_OBJS(pdev), "Failed to query available system memory");
uint64_t sysmem_available_B = 0;
if (!os_get_total_physical_memory(&sysmem_size_B) ||
!os_get_available_system_memory(&sysmem_available_B))
return 0;
}
/* Use 3/4 of available to avoid swapping */
return ROUND_DOWN_TO(sysmem_size_B * 3 / 4, 1 << 20);
uint64_t sysmem_used_B = sysmem_size_B - sysmem_available_B;
uint64_t sysmem_budget_B =
mtl_device_recommended_max_working_set_size(pdev->mtl_dev_handle);
uint64_t remaining_budget_B = sysmem_budget_B > sysmem_used_B ?
sysmem_budget_B - sysmem_used_B : 0u;
return remaining_budget_B +
mtl_device_current_allocated_size(pdev->mtl_dev_handle);
}
static uint64_t
kk_get_sysmem_heap_used(struct kk_physical_device *pdev)
{
/* From the Vulkan 1.3.278 spec:
*
* "heapUsage is an array of VK_MAX_MEMORY_HEAPS VkDeviceSize
* values in which memory usages are returned, with one element
* for each memory heap. A heaps usage is an estimate of how
* much memory the process is currently using in that heap."
*
* From Metal documentation for currentAllocatedSize:
*
* The total amount of memory, in bytes, the GPU device is using for all
* of its resources.
*
* We can trivially report estimated heap usage using Metal's reported
* allocated size
*/
return mtl_device_current_allocated_size(pdev->mtl_dev_handle);
}
static void
@ -901,7 +957,8 @@ kk_enumerate_physical_devices(struct vk_instance *_instance)
pdev->mem_heaps[sysmem_heap_idx] = (struct kk_memory_heap){
.size = sysmem_size_B,
.flags = VK_MEMORY_HEAP_DEVICE_LOCAL_BIT,
.available = kk_get_sysmem_heap_available,
.budget = kk_get_sysmem_heap_budget,
.used = kk_get_sysmem_heap_used,
};
pdev->mem_types[pdev->mem_type_count++] = (VkMemoryType){
@ -987,46 +1044,8 @@ kk_GetPhysicalDeviceMemoryProperties2(
for (unsigned i = 0; i < pdev->mem_heap_count; i++) {
const struct kk_memory_heap *heap = &pdev->mem_heaps[i];
uint64_t used = p_atomic_read(&heap->used);
/* From the Vulkan 1.3.278 spec:
*
* "heapUsage is an array of VK_MAX_MEMORY_HEAPS VkDeviceSize
* values in which memory usages are returned, with one element
* for each memory heap. A heaps usage is an estimate of how
* much memory the process is currently using in that heap."
*
* TODO: Include internal allocations?
*/
p->heapUsage[i] = used;
uint64_t available = heap->size;
if (heap->available)
available = heap->available(pdev);
/* From the Vulkan 1.3.278 spec:
*
* "heapBudget is an array of VK_MAX_MEMORY_HEAPS VkDeviceSize
* values in which memory budgets are returned, with one
* element for each memory heap. A heaps budget is a rough
* estimate of how much memory the process can allocate from
* that heap before allocations may fail or cause performance
* degradation. The budget includes any currently allocated
* device memory."
*
* and
*
* "The heapBudget value must be less than or equal to
* VkMemoryHeap::size for each heap."
*
* available (queried above) is the total amount free memory
* system-wide and does not include our allocations so we need
* to add that in.
*/
uint64_t budget = MIN2(available + used, heap->size);
/* Set the budget at 90% of available to avoid thrashing */
p->heapBudget[i] = ROUND_DOWN_TO(budget * 9 / 10, 1 << 20);
p->heapBudget[i] = heap->budget ? heap->budget(pdev) : 0;
p->heapUsage[i] = heap->used ? heap->used(pdev) : 0;
}
/* From the Vulkan 1.3.278 spec:

View file

@ -30,9 +30,9 @@ struct kk_queue_family {
struct kk_memory_heap {
uint64_t size;
uint64_t used;
VkMemoryHeapFlags flags;
uint64_t (*available)(struct kk_physical_device *pdev);
uint64_t (*budget)(struct kk_physical_device *pdev);
uint64_t (*used)(struct kk_physical_device *pdev);
};
struct kk_device_info {