• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright © 2017 Google
3  * Copyright © 2019 Red Hat
4  * Copyright © 2024 Igalia S.L.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the "Software"),
8  * to deal in the Software without restriction, including without limitation
9  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10  * and/or sell copies of the Software, and to permit persons to whom the
11  * Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice (including the next
14  * paragraph) shall be included in all copies or substantial portions of the
15  * Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
20  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
23  * IN THE SOFTWARE.
24  */
25 
26 #include <assert.h>
27 #include <errno.h>
28 #include <inttypes.h>
29 #include <limits.h>
30 #include <math.h>
31 #include <stddef.h>
32 #include <stdint.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <vulkan/vk_layer.h>
36 #include <vulkan/vulkan_core.h>
37 
38 #include "util/bitscan.h"
39 #include "util/hash_table.h"
40 #include "util/macros.h"
41 #include "util/os_memory.h"
42 #include "util/os_misc.h"
43 #include "util/simple_mtx.h"
44 #include "util/u_memory.h"
45 #include "vk_dispatch_table.h"
46 #include "vk_enum_to_str.h"
47 #include "vk_util.h"
48 
49 #define KiB(v) (UINT64_C(1024) * (v))
50 #define MiB(v) (UINT64_C(1024) * KiB(v))
51 
52 #define VRAM_REPORT_LIMIT_DEBUG_LOG_TAG "VRAM-REPORT-LIMIT DEBUG: "
53 #define VRAM_REPORT_LIMIT_WARN_LOG_TAG  "VRAM-REPORT-LIMIT WARNING: "
54 #define VRAM_REPORT_LIMIT_ERROR_LOG_TAG "VRAM-REPORT-LIMIT ERROR: "
55 
56 struct vram_report_limit_instance_data {
57    struct vk_instance_dispatch_table vtable;
58    struct vk_physical_device_dispatch_table pd_vtable;
59    VkInstance instance;
60 
61    /* Used to indicate that the heap size is unaffected. I.e. the layer will use
62     * the size reported by the underlying driver.
63     */
64 #define VRAM_REPORT_LIMIT_STATIC_HEAP_SIZE_DEFAULT (0)
65    uint64_t static_heap_size;
66 
67    uint32_t active_pdevices_count;
68    struct vram_report_limit_pdevice_data {
69       VkPhysicalDevice pdevice;
70       /* Percentage to scale each device heap's reported budged.
71        * 1.0 is 100%.
72        */
73       long double per_heap_budget_percentage[VK_MAX_MEMORY_HEAPS];
74    } active_pdevices_array[];
75 };
76 
77 #define HKEY(obj)       ((uint64_t)(obj))
78 #define FIND(type, obj) ((type *)find_object_data(HKEY(obj)))
79 
80 static struct hash_table_u64 *vk_object_to_data = NULL;
81 static simple_mtx_t vk_object_to_data_mutex = SIMPLE_MTX_INITIALIZER;
82 
83 static inline void
ensure_vk_object_map(void)84 ensure_vk_object_map(void)
85 {
86    if (!vk_object_to_data) {
87       vk_object_to_data = _mesa_hash_table_u64_create(NULL);
88    }
89 }
90 
91 static void *
find_object_data(uint64_t obj)92 find_object_data(uint64_t obj)
93 {
94    simple_mtx_lock(&vk_object_to_data_mutex);
95    ensure_vk_object_map();
96    void *data = _mesa_hash_table_u64_search(vk_object_to_data, obj);
97    simple_mtx_unlock(&vk_object_to_data_mutex);
98    return data;
99 }
100 
101 static void
map_object(uint64_t obj,void * data)102 map_object(uint64_t obj, void *data)
103 {
104    simple_mtx_lock(&vk_object_to_data_mutex);
105    ensure_vk_object_map();
106    _mesa_hash_table_u64_insert(vk_object_to_data, obj, data);
107    simple_mtx_unlock(&vk_object_to_data_mutex);
108 }
109 
110 static void
unmap_object(uint64_t obj)111 unmap_object(uint64_t obj)
112 {
113    simple_mtx_lock(&vk_object_to_data_mutex);
114    _mesa_hash_table_u64_remove(vk_object_to_data, obj);
115    simple_mtx_unlock(&vk_object_to_data_mutex);
116 }
117 
118 #define VK_VRAM_REPORT_LIMIT_HEAP_SIZE_ENV_VAR_NAME                            \
119    "VK_VRAM_REPORT_LIMIT_HEAP_SIZE"
120 
121 static uint64_t
vram_report_limit_env_get_static_heap_size_or_default()122 vram_report_limit_env_get_static_heap_size_or_default()
123 {
124    const char *const env_var_value_str =
125       os_get_option(VK_VRAM_REPORT_LIMIT_HEAP_SIZE_ENV_VAR_NAME);
126    if (!env_var_value_str) {
127       goto err_return;
128    }
129 
130    const char *start_ptr = env_var_value_str;
131    char *end_ptr;
132 
133    errno = 0;
134    const unsigned long long env_var_value =
135       strtoull(env_var_value_str, &end_ptr, 0);
136    if ((env_var_value == 0 && end_ptr == start_ptr) || errno == EINVAL ||
137        errno == ERANGE) {
138       goto err_return;
139    }
140 
141    if (env_var_value == 0) {
142       return VRAM_REPORT_LIMIT_STATIC_HEAP_SIZE_DEFAULT;
143    }
144 
145    return MiB(env_var_value);
146 
147 err_return:
148    fprintf(
149       stderr,
150       VRAM_REPORT_LIMIT_ERROR_LOG_TAG VK_VRAM_REPORT_LIMIT_HEAP_SIZE_ENV_VAR_NAME
151       " is invalid or not set.\n");
152 
153    return VRAM_REPORT_LIMIT_STATIC_HEAP_SIZE_DEFAULT;
154 }
155 
156 #undef VK_VRAM_REPORT_LIMIT_HEAP_SIZE_ENV_VAR_NAME
157 
158 #define VK_VRAM_REPORT_LIMIT_DEVICE_ID_ENV_VAR_NAME                            \
159    "VK_VRAM_REPORT_LIMIT_DEVICE_ID"
160 
161 static bool
vram_report_limit_env_get_device_id(VkVendorId * vendor_id_out,uint32_t * device_id_out)162 vram_report_limit_env_get_device_id(VkVendorId *vendor_id_out,
163                                     uint32_t *device_id_out)
164 {
165    const char *const env_var_value_str =
166       os_get_option(VK_VRAM_REPORT_LIMIT_DEVICE_ID_ENV_VAR_NAME);
167    if (!env_var_value_str) {
168       goto err_return;
169    }
170 
171    char *end_ptr;
172 
173    errno = 0;
174    unsigned long val_0 = strtoul(env_var_value_str, &end_ptr, 0);
175    if (errno == EINVAL || errno == ERANGE || end_ptr == env_var_value_str) {
176       goto err_return;
177    }
178 
179    char *start_ptr = end_ptr;
180 
181    if (*start_ptr != ':') {
182       goto err_return;
183    }
184 
185    start_ptr++;
186 
187    errno = 0;
188    unsigned long val_1 = strtoul(start_ptr, &end_ptr, 0);
189    if (errno == EINVAL || errno == ERANGE || end_ptr == start_ptr)
190       return false;
191 
192    *vendor_id_out = val_0;
193    *device_id_out = val_1;
194 
195    return true;
196 
197 err_return:
198    fprintf(
199       stderr,
200       VRAM_REPORT_LIMIT_ERROR_LOG_TAG VK_VRAM_REPORT_LIMIT_DEVICE_ID_ENV_VAR_NAME
201       " is invalid or not set.\n");
202    return false;
203 }
204 
205 #undef VK_VRAM_REPORT_LIMIT_DEVICE_ID_ENV_VAR_NAME
206 
207 static void
vram_report_limit_get_memory_heaps_with_device_property(VkPhysicalDeviceMemoryProperties * memory_properties,uint32_t * heaps_bitmask_out,VkMemoryHeap * heaps_out[static const VK_MAX_MEMORY_HEAPS])208 vram_report_limit_get_memory_heaps_with_device_property(
209    VkPhysicalDeviceMemoryProperties *memory_properties,
210    uint32_t *heaps_bitmask_out,
211    VkMemoryHeap *heaps_out[static const VK_MAX_MEMORY_HEAPS])
212 {
213    uint32_t heaps_bitmask = 0;
214 
215    STATIC_ASSERT(sizeof(heaps_bitmask) * CHAR_BIT >= VK_MAX_MEMORY_HEAPS);
216 
217    for (uint32_t i = 0; i < VK_MAX_MEMORY_HEAPS; i++) {
218       heaps_out[i] = NULL;
219    }
220 
221    for (uint32_t i = 0; i < memory_properties->memoryTypeCount; i++) {
222       const VkMemoryType *const memory_type =
223          &memory_properties->memoryTypes[i];
224 
225 #if !defined(NDEBUG)
226       const VkMemoryPropertyFlags handled_mem_flags =
227          VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
228          VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
229          VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
230          VK_MEMORY_PROPERTY_HOST_CACHED_BIT |
231          VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT |
232          VK_MEMORY_PROPERTY_PROTECTED_BIT;
233 
234       u_foreach_bit (mem_flag,
235                      memory_type->propertyFlags & ~handled_mem_flags) {
236          fprintf(stderr,
237                  VRAM_REPORT_LIMIT_WARN_LOG_TAG
238                  "unhandled VkMemoryPropertyFlagBits: %s\n",
239                  vk_MemoryPropertyFlagBits_to_str(mem_flag));
240       }
241 #endif
242 
243       const VkMemoryPropertyFlags device_mem_flags =
244          VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
245          VK_MEMORY_PROPERTY_PROTECTED_BIT |
246          VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT;
247 
248       if (!(memory_type->propertyFlags & device_mem_flags)) {
249          continue;
250       }
251 
252       const uint32_t heap_index = memory_type->heapIndex;
253 
254       /* From the Vulkan spec:
255        *
256        *   "More than one memory type may share each heap"
257        *
258        * So we don't accidentally want to get the same heap again.
259        */
260       if (heaps_bitmask & BITFIELD_BIT(heap_index)) {
261          continue;
262       }
263 
264       heaps_bitmask |= BITFIELD_BIT(heap_index);
265       heaps_out[heap_index] = &memory_properties->memoryHeaps[heap_index];
266    }
267 
268    *heaps_bitmask_out = heaps_bitmask;
269 }
270 
271 static void
destroy_instance_data(struct vram_report_limit_instance_data * data)272 destroy_instance_data(struct vram_report_limit_instance_data *data)
273 {
274    unmap_object(HKEY(data->instance));
275    os_free_aligned(data);
276 }
277 
278 static void
instance_data_unmap_physical_devices(struct vram_report_limit_instance_data * instance_data)279 instance_data_unmap_physical_devices(
280    struct vram_report_limit_instance_data *instance_data)
281 {
282    uint32_t physicalDeviceCount = 0;
283 
284    instance_data->vtable.EnumeratePhysicalDevices(instance_data->instance,
285                                                   &physicalDeviceCount, NULL);
286    if (physicalDeviceCount == 0) {
287       return;
288    }
289 
290    VkPhysicalDevice *physicalDevices =
291       os_malloc(sizeof(VkPhysicalDevice) * physicalDeviceCount);
292    if (physicalDevices == NULL) {
293       return;
294    }
295 
296    instance_data->vtable.EnumeratePhysicalDevices(
297       instance_data->instance, &physicalDeviceCount, physicalDevices);
298    assert(physicalDeviceCount > 0);
299 
300    for (uint32_t i = 0; i < physicalDeviceCount; i++) {
301       unmap_object(HKEY(physicalDevices[i]));
302    }
303 
304    os_free(physicalDevices);
305 }
306 
307 static VkLayerInstanceCreateInfo *
get_instance_chain_info(const VkInstanceCreateInfo * pCreateInfo)308 get_instance_chain_info(const VkInstanceCreateInfo *pCreateInfo)
309 {
310    vk_foreach_struct_const (item, pCreateInfo->pNext) {
311       if (item->sType == VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO &&
312           ((VkLayerInstanceCreateInfo *)item)->function == VK_LAYER_LINK_INFO)
313          return (VkLayerInstanceCreateInfo *)item;
314    }
315    unreachable("instance chain info not found");
316    return NULL;
317 }
318 
319 static VkResult
vram_report_limit_CreateInstance(const VkInstanceCreateInfo * pCreateInfo,const VkAllocationCallbacks * pAllocator,VkInstance * pInstance)320 vram_report_limit_CreateInstance(const VkInstanceCreateInfo *pCreateInfo,
321                                  const VkAllocationCallbacks *pAllocator,
322                                  VkInstance *pInstance)
323 {
324    VkResult result;
325 
326    VkLayerInstanceCreateInfo *chain_info = get_instance_chain_info(pCreateInfo);
327 
328    assert(chain_info->u.pLayerInfo);
329    PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr =
330       chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr;
331 
332 #define DEFINE_VK_VOID_FUNC_PTR(proc_addr_func, instance, func_name)           \
333    CONCAT2(PFN_vk, func_name)                                                  \
334    CONCAT2(fp, func_name) =                                                    \
335       (CONCAT2(PFN_vk, func_name))proc_addr_func(instance, "vk" #func_name)
336 
337    DEFINE_VK_VOID_FUNC_PTR(fpGetInstanceProcAddr, NULL, CreateInstance);
338    if (fpCreateInstance == NULL) {
339       result = VK_ERROR_INITIALIZATION_FAILED;
340       goto err_return;
341    }
342 
343    PFN_GetPhysicalDeviceProcAddr fpGetPhysicalDeviceProcAddr =
344       chain_info->u.pLayerInfo->pfnNextGetPhysicalDeviceProcAddr;
345    if (fpGetPhysicalDeviceProcAddr == NULL) {
346       result = VK_ERROR_INITIALIZATION_FAILED;
347       goto err_return;
348    }
349 
350    /* Advance the link info for the next element on the chain */
351    chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext;
352 
353    result = fpCreateInstance(pCreateInfo, pAllocator, pInstance);
354    if (result != VK_SUCCESS) {
355       goto err_return;
356    }
357 
358    DEFINE_VK_VOID_FUNC_PTR(fpGetInstanceProcAddr, *pInstance, DestroyInstance);
359    if (fpDestroyInstance == NULL) {
360       result = VK_ERROR_INITIALIZATION_FAILED;
361       goto err_return;
362    }
363 
364    DEFINE_VK_VOID_FUNC_PTR(fpGetInstanceProcAddr, *pInstance,
365                            EnumeratePhysicalDevices);
366    if (fpEnumeratePhysicalDevices == NULL) {
367       result = VK_ERROR_INITIALIZATION_FAILED;
368       goto err_destroy_instance;
369    }
370 
371    DEFINE_VK_VOID_FUNC_PTR(fpGetPhysicalDeviceProcAddr, *pInstance,
372                            GetPhysicalDeviceProperties);
373    if (fpGetPhysicalDeviceProperties == NULL) {
374       result = VK_ERROR_INITIALIZATION_FAILED;
375       goto err_destroy_instance;
376    }
377 
378 #undef DEFINE_VK_VOID_FUNC_PTR
379 
380    const uint64_t static_heap_size =
381       vram_report_limit_env_get_static_heap_size_or_default();
382 
383    VkVendorId vendor_id = ~0;
384    uint32_t device_id = ~0;
385    const bool device_id_is_valid =
386       vram_report_limit_env_get_device_id(&vendor_id, &device_id);
387 
388    uint32_t pdevice_count = 0;
389    fpEnumeratePhysicalDevices(*pInstance, &pdevice_count, NULL);
390 
391    VkPhysicalDevice *pdevices_array = NULL;
392    bool *is_pdevice_active_array = NULL;
393    if (pdevice_count > 0) {
394       pdevices_array = os_malloc(sizeof(VkPhysicalDevice) * pdevice_count);
395       if (pdevices_array == NULL) {
396          result = VK_ERROR_OUT_OF_HOST_MEMORY;
397          goto err_destroy_instance;
398       }
399 
400       fpEnumeratePhysicalDevices(*pInstance, &pdevice_count, pdevices_array);
401 
402       is_pdevice_active_array =
403          (bool *)os_calloc(pdevice_count, sizeof(*is_pdevice_active_array));
404       if (is_pdevice_active_array == NULL) {
405          result = VK_ERROR_OUT_OF_HOST_MEMORY;
406          goto err_free_pdevices_array;
407       }
408    }
409 
410    uint32_t active_pdevices_count = 0;
411    if (device_id_is_valid &&
412        static_heap_size != VRAM_REPORT_LIMIT_STATIC_HEAP_SIZE_DEFAULT) {
413       for (uint32_t i = 0; i < pdevice_count; i++) {
414          VkPhysicalDevice pdevice = pdevices_array[i];
415          VkPhysicalDeviceProperties properties;
416 
417          is_pdevice_active_array[i] = false;
418 
419          fpGetPhysicalDeviceProperties(pdevice, &properties);
420 
421          if (properties.vendorID != vendor_id) {
422             continue;
423          }
424 
425          if (properties.deviceID != device_id) {
426             continue;
427          }
428 
429 #if defined(DEBUG)
430          printf(VRAM_REPORT_LIMIT_DEBUG_LOG_TAG "Active device: %s\n",
431                 properties.deviceName);
432          printf(VRAM_REPORT_LIMIT_DEBUG_LOG_TAG "Static Heap size: %lu MiB\n",
433                 static_heap_size / MiB(1));
434 #endif
435 
436          is_pdevice_active_array[i] = true;
437          active_pdevices_count++;
438       }
439    }
440 
441    if (active_pdevices_count == 0 &&
442        static_heap_size != VRAM_REPORT_LIMIT_STATIC_HEAP_SIZE_DEFAULT) {
443       fprintf(stderr, VRAM_REPORT_LIMIT_WARN_LOG_TAG
444               "No device found to apply the limit to.\n");
445    }
446 
447    struct vram_report_limit_instance_data *instance_data = os_malloc_aligned(
448       sizeof(*instance_data) + sizeof(instance_data->active_pdevices_array[0]) *
449                                   active_pdevices_count,
450       CACHE_LINE_SIZE);
451    if (instance_data == NULL) {
452       result = VK_ERROR_OUT_OF_HOST_MEMORY;
453       goto err_free_is_pdevice_active_array;
454    }
455 
456    vk_instance_dispatch_table_load(&instance_data->vtable,
457                                    fpGetInstanceProcAddr, *pInstance);
458    vk_physical_device_dispatch_table_load(&instance_data->pd_vtable,
459                                           fpGetInstanceProcAddr, *pInstance);
460 
461    instance_data->instance = *pInstance;
462    instance_data->static_heap_size = static_heap_size;
463 
464    instance_data->active_pdevices_count = 0;
465    for (uint32_t i = 0; i < pdevice_count; i++) {
466       VkPhysicalDevice pdevice = pdevices_array[i];
467       struct vram_report_limit_pdevice_data *pdevice_data;
468 
469       /* Even though multiple physical devices have the same vendor id and
470        * device id, they might not have the same heap arrangements due to
471        * potentially differing drivers. So we have to maintain per pdevice
472        * budged percentages and not just calculate it once to be used with
473        * all.
474        */
475       if (!is_pdevice_active_array[i]) {
476          continue;
477       }
478 
479       /* No device should be active if the default size is set. */
480       assert(static_heap_size != VRAM_REPORT_LIMIT_STATIC_HEAP_SIZE_DEFAULT);
481 
482       pdevice_data =
483          &instance_data
484              ->active_pdevices_array[instance_data->active_pdevices_count];
485       instance_data->active_pdevices_count++;
486 
487       pdevice_data->pdevice = pdevice;
488 
489       if (instance_data->pd_vtable.GetPhysicalDeviceMemoryProperties2 == NULL) {
490 #if defined(DEBUG)
491          for (uint32_t i = 0; i < VK_MAX_MEMORY_HEAPS; i++)
492             pdevice_data->per_heap_budget_percentage[i] = NAN;
493 #endif
494 
495          continue;
496       }
497 
498       /* For each active device we need to setup a budget percentage to scale
499        * down the reported budget to keep it under the new heap size.
500        */
501       VkPhysicalDeviceMemoryProperties2 memory_properties = {
502          .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2,
503       };
504 
505       instance_data->pd_vtable.GetPhysicalDeviceMemoryProperties2(
506          pdevice, &memory_properties);
507 
508       VkMemoryHeap *heaps_array[VK_MAX_MEMORY_HEAPS];
509       uint32_t heaps_array_bitmask;
510 
511       vram_report_limit_get_memory_heaps_with_device_property(
512          &memory_properties.memoryProperties, &heaps_array_bitmask,
513          heaps_array);
514 
515       STATIC_ASSERT(ARRAY_SIZE(instance_data->active_pdevices_array[0]
516                                   .per_heap_budget_percentage) ==
517                     VK_MAX_MEMORY_HEAPS);
518       for (uint32_t i = 0; i < VK_MAX_MEMORY_HEAPS; i++) {
519          const VkMemoryHeap *const heap = heaps_array[i];
520 
521          if (!(BITFIELD_BIT(i) & heaps_array_bitmask)) {
522             pdevice_data->per_heap_budget_percentage[i] = 1.0;
523             continue;
524          }
525 
526          assert(static_heap_size != VRAM_REPORT_LIMIT_STATIC_HEAP_SIZE_DEFAULT);
527          const long double ratio =
528             (long double)instance_data->static_heap_size / heap->size;
529 
530          pdevice_data->per_heap_budget_percentage[i] = ratio;
531       }
532    }
533 
534    map_object(HKEY(instance_data->instance), instance_data);
535 
536    for (uint32_t i = 0; i < pdevice_count; i++) {
537       map_object(HKEY(pdevices_array[i]), instance_data);
538    }
539 
540    if (is_pdevice_active_array) {
541       os_free(is_pdevice_active_array);
542    }
543 
544    if (pdevices_array) {
545       os_free(pdevices_array);
546    }
547 
548    return VK_SUCCESS;
549 
550 err_free_is_pdevice_active_array:
551    if (is_pdevice_active_array) {
552       os_free(is_pdevice_active_array);
553    }
554 
555 err_free_pdevices_array:
556    if (pdevices_array) {
557       os_free(pdevices_array);
558    }
559 
560 err_destroy_instance:
561    fpDestroyInstance(*pInstance, NULL);
562 
563 err_return:
564    return result;
565 }
566 
567 static void
vram_report_limit_DestroyInstance(VkInstance instance,const VkAllocationCallbacks * pAllocator)568 vram_report_limit_DestroyInstance(VkInstance instance,
569                                   const VkAllocationCallbacks *pAllocator)
570 {
571    struct vram_report_limit_instance_data *const instance_data =
572       FIND(struct vram_report_limit_instance_data, instance);
573 
574    instance_data_unmap_physical_devices(instance_data);
575    instance_data->vtable.DestroyInstance(instance, pAllocator);
576 
577    destroy_instance_data(instance_data);
578 }
579 
580 static inline void
vram_report_limit_apply_budget_percentage(long double percentage,VkDeviceSize * const size_in_out)581 vram_report_limit_apply_budget_percentage(long double percentage,
582                                           VkDeviceSize *const size_in_out)
583 {
584    const VkDeviceSize old_size = *size_in_out;
585    const VkDeviceSize new_size = (VkDeviceSize)(old_size * percentage);
586 
587 #if defined(DEBUG)
588    if (percentage != 1.0) {
589       printf(VRAM_REPORT_LIMIT_DEBUG_LOG_TAG
590              "tweaking budget size to %0.2Lf %%, %" PRIu64 " MiB -> %" PRIu64
591              " MiB\n",
592              percentage * 100, old_size / MiB(1), new_size / MiB(1));
593    }
594 #endif
595 
596    *size_in_out = new_size;
597 }
598 
599 static void
vram_report_limit_tweak_memory_properties(const struct vram_report_limit_instance_data * instance_data,VkPhysicalDevice pdevice,VkPhysicalDeviceMemoryProperties * memory_properties,VkPhysicalDeviceMemoryBudgetPropertiesEXT * memory_budget_optional)600 vram_report_limit_tweak_memory_properties(
601    const struct vram_report_limit_instance_data *instance_data,
602    VkPhysicalDevice pdevice,
603    VkPhysicalDeviceMemoryProperties *memory_properties,
604    VkPhysicalDeviceMemoryBudgetPropertiesEXT *memory_budget_optional)
605 {
606    if (instance_data->static_heap_size ==
607        VRAM_REPORT_LIMIT_STATIC_HEAP_SIZE_DEFAULT) {
608       return;
609    }
610 
611    const struct vram_report_limit_pdevice_data *pdevice_data = NULL;
612    for (uint32_t i = 0; i < instance_data->active_pdevices_count; i++) {
613       if (instance_data->active_pdevices_array[i].pdevice != pdevice) {
614          continue;
615       }
616 
617       pdevice_data = &instance_data->active_pdevices_array[i];
618    }
619 
620    if (pdevice_data == NULL) {
621       /* The device wasn't selected by the user so don't tweak any values. */
622       return;
623    }
624 
625    for (uint32_t i = 0; i < VK_MAX_MEMORY_HEAPS; i++) {
626       if (i > memory_properties->memoryHeapCount) {
627          break;
628       }
629 
630       assert(instance_data->static_heap_size !=
631              VRAM_REPORT_LIMIT_STATIC_HEAP_SIZE_DEFAULT);
632       memory_properties->memoryHeaps[i].size = instance_data->static_heap_size;
633 
634       if (memory_budget_optional) {
635          const long double percentage =
636             pdevice_data->per_heap_budget_percentage[i];
637 
638          vram_report_limit_apply_budget_percentage(
639             percentage, &memory_budget_optional->heapBudget[i]);
640 
641          assert(memory_budget_optional->heapBudget[i] <=
642                 memory_properties->memoryHeaps[i].size);
643       }
644    }
645 }
646 
647 static VKAPI_ATTR void VKAPI_CALL
vram_report_limit_GetPhysicalDeviceMemoryProperties(VkPhysicalDevice physicalDevice,VkPhysicalDeviceMemoryProperties * pMemoryProperties)648 vram_report_limit_GetPhysicalDeviceMemoryProperties(
649    VkPhysicalDevice physicalDevice,
650    VkPhysicalDeviceMemoryProperties *pMemoryProperties)
651 {
652    struct vram_report_limit_instance_data *instance_data =
653       FIND(struct vram_report_limit_instance_data, physicalDevice);
654 
655    instance_data->pd_vtable.GetPhysicalDeviceMemoryProperties(
656       physicalDevice, pMemoryProperties);
657 
658    vram_report_limit_tweak_memory_properties(instance_data, physicalDevice,
659                                              pMemoryProperties, NULL);
660 }
661 
662 static VKAPI_ATTR void VKAPI_CALL
vram_report_limit_GetPhysicalDeviceMemoryProperties2(VkPhysicalDevice physicalDevice,VkPhysicalDeviceMemoryProperties2 * pMemoryProperties)663 vram_report_limit_GetPhysicalDeviceMemoryProperties2(
664    VkPhysicalDevice physicalDevice,
665    VkPhysicalDeviceMemoryProperties2 *pMemoryProperties)
666 {
667    struct vram_report_limit_instance_data *instance_data =
668       FIND(struct vram_report_limit_instance_data, physicalDevice);
669 
670    instance_data->pd_vtable.GetPhysicalDeviceMemoryProperties2(
671       physicalDevice, pMemoryProperties);
672 
673    struct VkPhysicalDeviceMemoryBudgetPropertiesEXT *budget_properties =
674       vk_find_struct(pMemoryProperties->pNext,
675                      PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT);
676 
677    vram_report_limit_tweak_memory_properties(
678       instance_data, physicalDevice, &pMemoryProperties->memoryProperties,
679       budget_properties);
680 }
681 
682 static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
683 vram_report_limit_GetInstanceProcAddr(VkInstance instance,
684                                       const char *funcName);
685 
686 static void *
find_ptr(const char * name)687 find_ptr(const char *name)
688 {
689    static const struct {
690       const char *name;
691       void *ptr;
692    } name_to_funcptr_map[] = {
693       {"vkGetInstanceProcAddr", (void *)vram_report_limit_GetInstanceProcAddr},
694 #define ADD_HOOK(fn) {"vk" #fn, (void *)vram_report_limit_##fn}
695 #define ADD_ALIAS_HOOK(alias, fn)                                              \
696    {                                                                           \
697       "vk" #alias, (void *)vram_report_limit_##fn                              \
698    }
699       ADD_HOOK(GetPhysicalDeviceMemoryProperties),
700       ADD_HOOK(GetPhysicalDeviceMemoryProperties2),
701       ADD_ALIAS_HOOK(GetPhysicalDeviceMemoryProperties2KHR,
702                      GetPhysicalDeviceMemoryProperties2),
703 
704       ADD_HOOK(CreateInstance),
705       ADD_HOOK(DestroyInstance),
706 #undef ADD_HOOK
707 #undef ADD_ALIAS_HOOK
708    };
709 
710    for (uint32_t i = 0; i < ARRAY_SIZE(name_to_funcptr_map); i++) {
711       if (strcmp(name, name_to_funcptr_map[i].name) == 0) {
712          return name_to_funcptr_map[i].ptr;
713       }
714    }
715 
716    return NULL;
717 }
718 
719 static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
vram_report_limit_GetInstanceProcAddr(VkInstance instance,const char * funcName)720 vram_report_limit_GetInstanceProcAddr(VkInstance instance, const char *funcName)
721 {
722    void *ptr = find_ptr(funcName);
723    if (ptr) {
724       return (PFN_vkVoidFunction)(ptr);
725    }
726 
727    if (instance == NULL) {
728       return NULL;
729    }
730 
731    struct vram_report_limit_instance_data *instance_data =
732       FIND(struct vram_report_limit_instance_data, instance);
733    if (instance_data->vtable.GetInstanceProcAddr == NULL) {
734       return NULL;
735    }
736 
737    return instance_data->vtable.GetInstanceProcAddr(instance, funcName);
738 }
739 
740 PUBLIC VkResult
vkNegotiateLoaderLayerInterfaceVersion(VkNegotiateLayerInterface * pVersionStruct)741 vkNegotiateLoaderLayerInterfaceVersion(VkNegotiateLayerInterface *pVersionStruct)
742 {
743    if (pVersionStruct->loaderLayerInterfaceVersion < 2)
744       return VK_ERROR_INITIALIZATION_FAILED;
745 
746    pVersionStruct->loaderLayerInterfaceVersion = 2;
747    pVersionStruct->pfnGetInstanceProcAddr =
748       vram_report_limit_GetInstanceProcAddr;
749 
750    return VK_SUCCESS;
751 }
752