mirror of
https://gitlab.freedesktop.org/mesa/mesa.git
synced 2025-12-20 13:50:11 +01:00
vulkan: Add inital vram-report-limit layer
This Vulkan layer allows reporting a limited VRAM size to the application. This layer can be useful for testing applications and games which query for the memory budget adjusting their behavior accordingly. They layer does not set a hard limit on the amount of VRAM thus applications can still make allocations even though the reported budget might indicate no memory is left, if the set limit is lower than of actually available VRAM. Signed-off-by: Karmjit Mahil <karmjit.mahil@igalia.com> Reviewed-By: Mike Blumenkrantz <michael.blumenkrantz@gmail.com> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/30095>
This commit is contained in:
parent
ad603d300d
commit
1f9d96ec78
7 changed files with 841 additions and 1 deletions
|
|
@ -70,6 +70,7 @@ with_osmesa = get_option('osmesa')
|
||||||
with_vulkan_overlay_layer = get_option('vulkan-layers').contains('overlay')
|
with_vulkan_overlay_layer = get_option('vulkan-layers').contains('overlay')
|
||||||
with_vulkan_device_select_layer = get_option('vulkan-layers').contains('device-select')
|
with_vulkan_device_select_layer = get_option('vulkan-layers').contains('device-select')
|
||||||
with_vulkan_screenshot_layer = get_option('vulkan-layers').contains('screenshot')
|
with_vulkan_screenshot_layer = get_option('vulkan-layers').contains('screenshot')
|
||||||
|
with_vulkan_vram_report_limit_layer = get_option('vulkan-layers').contains('vram-report-limit')
|
||||||
with_tools = get_option('tools')
|
with_tools = get_option('tools')
|
||||||
if with_tools.contains('all')
|
if with_tools.contains('all')
|
||||||
with_tools = [
|
with_tools = [
|
||||||
|
|
|
||||||
|
|
@ -287,7 +287,10 @@ option(
|
||||||
'vulkan-layers',
|
'vulkan-layers',
|
||||||
type : 'array',
|
type : 'array',
|
||||||
value : [],
|
value : [],
|
||||||
choices : ['device-select', 'intel-nullhw', 'overlay', 'screenshot'],
|
choices : [
|
||||||
|
'device-select', 'intel-nullhw', 'overlay', 'screenshot',
|
||||||
|
'vram-report-limit',
|
||||||
|
],
|
||||||
description : 'List of vulkan layers to build'
|
description : 'List of vulkan layers to build'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,3 +95,6 @@ endif
|
||||||
if with_vulkan_screenshot_layer
|
if with_vulkan_screenshot_layer
|
||||||
subdir('screenshot-layer')
|
subdir('screenshot-layer')
|
||||||
endif
|
endif
|
||||||
|
if with_vulkan_vram_report_limit_layer
|
||||||
|
subdir('vram-report-limit-layer')
|
||||||
|
endif
|
||||||
|
|
|
||||||
6
src/vulkan/vram-report-limit-layer/.clang-format
Normal file
6
src/vulkan/vram-report-limit-layer/.clang-format
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
BasedOnStyle: InheritParentConfig
|
||||||
|
DisableFormat: false
|
||||||
|
|
||||||
|
AlignConsecutiveBitFields: true
|
||||||
|
ColumnLimit: 80
|
||||||
|
BreakStringLiterals: false
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"file_format_version": "1.0.0",
|
||||||
|
"layer": {
|
||||||
|
"name": "VK_LAYER_MESA_vram_report_limit",
|
||||||
|
"type": "GLOBAL",
|
||||||
|
"library_path": "libVkLayer_MESA_vram_report_limit.so",
|
||||||
|
"api_version": "1.4.303",
|
||||||
|
"implementation_version": "1",
|
||||||
|
"description": "Limit reported VRAM",
|
||||||
|
"features": {
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"key": "VK_VRAM_REPORT_LIMIT_DEVICE_ID",
|
||||||
|
"label": "VkPhysicalDevice ID",
|
||||||
|
"description":
|
||||||
|
"vendorID:deviceID identifying the VkPhysicalDevice for which to apply the VRAM adjustments.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "VK_VRAM_REPORT_LIMIT_HEAP_SIZE",
|
||||||
|
"label": "Heap Size in MiB",
|
||||||
|
"description":
|
||||||
|
"Static size in MiB to advertise for all device heaps for the selected physical devices. If 0, report the original size.",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"functions": {
|
||||||
|
"vkNegotiateLoaderLayerInterfaceVersion":
|
||||||
|
"vkNegotiateLoaderLayerInterfaceVersion"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/vulkan/vram-report-limit-layer/meson.build
Normal file
43
src/vulkan/vram-report-limit-layer/meson.build
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# Copyright © 2019 Intel Corporation
|
||||||
|
# Copyright © 2024 Igalia S.L.
|
||||||
|
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE.
|
||||||
|
|
||||||
|
vklayer_files = files(
|
||||||
|
'vram_report_limit_layer.c',
|
||||||
|
)
|
||||||
|
|
||||||
|
vklayer_mesa_vram_report_limit = shared_library(
|
||||||
|
'VkLayer_MESA_vram_report_limit',
|
||||||
|
vklayer_files,
|
||||||
|
c_args : no_override_init_args,
|
||||||
|
gnu_symbol_visibility : 'hidden',
|
||||||
|
dependencies : [
|
||||||
|
idep_vulkan_util, idep_mesautil
|
||||||
|
],
|
||||||
|
include_directories : [inc_include, inc_util, inc_src],
|
||||||
|
link_args : cc.get_supported_link_arguments(['-Wl,-Bsymbolic-functions', '-Wl,-z,relro']),
|
||||||
|
install : true
|
||||||
|
)
|
||||||
|
|
||||||
|
install_data(
|
||||||
|
files('VkLayer_MESA_vram_report_limit.json'),
|
||||||
|
install_dir : join_paths(get_option('datadir'), 'vulkan', 'explicit_layer.d'),
|
||||||
|
install_tag : 'runtime',
|
||||||
|
)
|
||||||
751
src/vulkan/vram-report-limit-layer/vram_report_limit_layer.c
Normal file
751
src/vulkan/vram-report-limit-layer/vram_report_limit_layer.c
Normal file
|
|
@ -0,0 +1,751 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2017 Google
|
||||||
|
* Copyright © 2019 Red Hat
|
||||||
|
* Copyright © 2024 Igalia S.L.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
* copy of this software and associated documentation files (the "Software"),
|
||||||
|
* to deal in the Software without restriction, including without limitation
|
||||||
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice (including the next
|
||||||
|
* paragraph) shall be included in all copies or substantial portions of the
|
||||||
|
* Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
* IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <vulkan/vk_layer.h>
|
||||||
|
#include <vulkan/vulkan_core.h>
|
||||||
|
|
||||||
|
#include "util/bitscan.h"
|
||||||
|
#include "util/hash_table.h"
|
||||||
|
#include "util/macros.h"
|
||||||
|
#include "util/os_memory.h"
|
||||||
|
#include "util/os_misc.h"
|
||||||
|
#include "util/simple_mtx.h"
|
||||||
|
#include "util/u_memory.h"
|
||||||
|
#include "vk_dispatch_table.h"
|
||||||
|
#include "vk_enum_to_str.h"
|
||||||
|
#include "vk_util.h"
|
||||||
|
|
||||||
|
#define KiB(v) (UINT64_C(1024) * (v))
|
||||||
|
#define MiB(v) (UINT64_C(1024) * KiB(v))
|
||||||
|
|
||||||
|
#define VRAM_REPORT_LIMIT_DEBUG_LOG_TAG "VRAM-REPORT-LIMIT DEBUG: "
|
||||||
|
#define VRAM_REPORT_LIMIT_WARN_LOG_TAG "VRAM-REPORT-LIMIT WARNING: "
|
||||||
|
#define VRAM_REPORT_LIMIT_ERROR_LOG_TAG "VRAM-REPORT-LIMIT ERROR: "
|
||||||
|
|
||||||
|
struct vram_report_limit_instance_data {
|
||||||
|
struct vk_instance_dispatch_table vtable;
|
||||||
|
struct vk_physical_device_dispatch_table pd_vtable;
|
||||||
|
VkInstance instance;
|
||||||
|
|
||||||
|
/* Used to indicate that the heap size is unaffected. I.e. the layer will use
|
||||||
|
* the size reported by the underlying driver.
|
||||||
|
*/
|
||||||
|
#define VRAM_REPORT_LIMIT_STATIC_HEAP_SIZE_DEFAULT (0)
|
||||||
|
uint64_t static_heap_size;
|
||||||
|
|
||||||
|
uint32_t active_pdevices_count;
|
||||||
|
struct vram_report_limit_pdevice_data {
|
||||||
|
VkPhysicalDevice pdevice;
|
||||||
|
/* Percentage to scale each device heap's reported budged.
|
||||||
|
* 1.0 is 100%.
|
||||||
|
*/
|
||||||
|
long double per_heap_budget_percentage[VK_MAX_MEMORY_HEAPS];
|
||||||
|
} active_pdevices_array[];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define HKEY(obj) ((uint64_t)(obj))
|
||||||
|
#define FIND(type, obj) ((type *)find_object_data(HKEY(obj)))
|
||||||
|
|
||||||
|
static struct hash_table_u64 *vk_object_to_data = NULL;
|
||||||
|
static simple_mtx_t vk_object_to_data_mutex = SIMPLE_MTX_INITIALIZER;
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
ensure_vk_object_map(void)
|
||||||
|
{
|
||||||
|
if (!vk_object_to_data) {
|
||||||
|
vk_object_to_data = _mesa_hash_table_u64_create(NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
find_object_data(uint64_t obj)
|
||||||
|
{
|
||||||
|
simple_mtx_lock(&vk_object_to_data_mutex);
|
||||||
|
ensure_vk_object_map();
|
||||||
|
void *data = _mesa_hash_table_u64_search(vk_object_to_data, obj);
|
||||||
|
simple_mtx_unlock(&vk_object_to_data_mutex);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
map_object(uint64_t obj, void *data)
|
||||||
|
{
|
||||||
|
simple_mtx_lock(&vk_object_to_data_mutex);
|
||||||
|
ensure_vk_object_map();
|
||||||
|
_mesa_hash_table_u64_insert(vk_object_to_data, obj, data);
|
||||||
|
simple_mtx_unlock(&vk_object_to_data_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
unmap_object(uint64_t obj)
|
||||||
|
{
|
||||||
|
simple_mtx_lock(&vk_object_to_data_mutex);
|
||||||
|
_mesa_hash_table_u64_remove(vk_object_to_data, obj);
|
||||||
|
simple_mtx_unlock(&vk_object_to_data_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define VK_VRAM_REPORT_LIMIT_HEAP_SIZE_ENV_VAR_NAME \
|
||||||
|
"VK_VRAM_REPORT_LIMIT_HEAP_SIZE"
|
||||||
|
|
||||||
|
static uint64_t
|
||||||
|
vram_report_limit_env_get_static_heap_size_or_default()
|
||||||
|
{
|
||||||
|
const char *const env_var_value_str =
|
||||||
|
os_get_option(VK_VRAM_REPORT_LIMIT_HEAP_SIZE_ENV_VAR_NAME);
|
||||||
|
if (!env_var_value_str) {
|
||||||
|
goto err_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *start_ptr = env_var_value_str;
|
||||||
|
char *end_ptr;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
const unsigned long long env_var_value =
|
||||||
|
strtoull(env_var_value_str, &end_ptr, 0);
|
||||||
|
if ((env_var_value == 0 && end_ptr == start_ptr) || errno == EINVAL ||
|
||||||
|
errno == ERANGE) {
|
||||||
|
goto err_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (env_var_value == 0) {
|
||||||
|
return VRAM_REPORT_LIMIT_STATIC_HEAP_SIZE_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MiB(env_var_value);
|
||||||
|
|
||||||
|
err_return:
|
||||||
|
fprintf(
|
||||||
|
stderr,
|
||||||
|
VRAM_REPORT_LIMIT_ERROR_LOG_TAG VK_VRAM_REPORT_LIMIT_HEAP_SIZE_ENV_VAR_NAME
|
||||||
|
" is invalid or not set.\n");
|
||||||
|
|
||||||
|
return VRAM_REPORT_LIMIT_STATIC_HEAP_SIZE_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef VK_VRAM_REPORT_LIMIT_HEAP_SIZE_ENV_VAR_NAME
|
||||||
|
|
||||||
|
#define VK_VRAM_REPORT_LIMIT_DEVICE_ID_ENV_VAR_NAME \
|
||||||
|
"VK_VRAM_REPORT_LIMIT_DEVICE_ID"
|
||||||
|
|
||||||
|
static bool
|
||||||
|
vram_report_limit_env_get_device_id(VkVendorId *vendor_id_out,
|
||||||
|
uint32_t *device_id_out)
|
||||||
|
{
|
||||||
|
const char *const env_var_value_str =
|
||||||
|
os_get_option(VK_VRAM_REPORT_LIMIT_DEVICE_ID_ENV_VAR_NAME);
|
||||||
|
if (!env_var_value_str) {
|
||||||
|
goto err_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *end_ptr;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
unsigned long val_0 = strtoul(env_var_value_str, &end_ptr, 0);
|
||||||
|
if (errno == EINVAL || errno == ERANGE || end_ptr == env_var_value_str) {
|
||||||
|
goto err_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *start_ptr = end_ptr;
|
||||||
|
|
||||||
|
if (*start_ptr != ':') {
|
||||||
|
goto err_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
start_ptr++;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
unsigned long val_1 = strtoul(start_ptr, &end_ptr, 0);
|
||||||
|
if (errno == EINVAL || errno == ERANGE || end_ptr == start_ptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*vendor_id_out = val_0;
|
||||||
|
*device_id_out = val_1;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
err_return:
|
||||||
|
fprintf(
|
||||||
|
stderr,
|
||||||
|
VRAM_REPORT_LIMIT_ERROR_LOG_TAG VK_VRAM_REPORT_LIMIT_DEVICE_ID_ENV_VAR_NAME
|
||||||
|
" is invalid or not set.\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef VK_VRAM_REPORT_LIMIT_DEVICE_ID_ENV_VAR_NAME
|
||||||
|
|
||||||
|
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])
|
||||||
|
{
|
||||||
|
uint32_t heaps_bitmask = 0;
|
||||||
|
|
||||||
|
STATIC_ASSERT(sizeof(heaps_bitmask) * CHAR_BIT >= VK_MAX_MEMORY_HEAPS);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < VK_MAX_MEMORY_HEAPS; i++) {
|
||||||
|
heaps_out[i] = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < memory_properties->memoryTypeCount; i++) {
|
||||||
|
const VkMemoryType *const memory_type =
|
||||||
|
&memory_properties->memoryTypes[i];
|
||||||
|
|
||||||
|
#if !defined(NDEBUG)
|
||||||
|
const VkMemoryPropertyFlags handled_mem_flags =
|
||||||
|
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
|
||||||
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
||||||
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
|
||||||
|
VK_MEMORY_PROPERTY_HOST_CACHED_BIT |
|
||||||
|
VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT |
|
||||||
|
VK_MEMORY_PROPERTY_PROTECTED_BIT;
|
||||||
|
|
||||||
|
u_foreach_bit (mem_flag,
|
||||||
|
memory_type->propertyFlags & ~handled_mem_flags) {
|
||||||
|
fprintf(stderr,
|
||||||
|
VRAM_REPORT_LIMIT_WARN_LOG_TAG
|
||||||
|
"unhandled VkMemoryPropertyFlagBits: %s\n",
|
||||||
|
vk_MemoryPropertyFlagBits_to_str(mem_flag));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const VkMemoryPropertyFlags device_mem_flags =
|
||||||
|
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
|
||||||
|
VK_MEMORY_PROPERTY_PROTECTED_BIT |
|
||||||
|
VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT;
|
||||||
|
|
||||||
|
if (!(memory_type->propertyFlags & device_mem_flags)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t heap_index = memory_type->heapIndex;
|
||||||
|
|
||||||
|
/* From the Vulkan spec:
|
||||||
|
*
|
||||||
|
* "More than one memory type may share each heap"
|
||||||
|
*
|
||||||
|
* So we don't accidentally want to get the same heap again.
|
||||||
|
*/
|
||||||
|
if (heaps_bitmask & BITFIELD_BIT(heap_index)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
heaps_bitmask |= BITFIELD_BIT(heap_index);
|
||||||
|
heaps_out[heap_index] = &memory_properties->memoryHeaps[heap_index];
|
||||||
|
}
|
||||||
|
|
||||||
|
*heaps_bitmask_out = heaps_bitmask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
destroy_instance_data(struct vram_report_limit_instance_data *data)
|
||||||
|
{
|
||||||
|
unmap_object(HKEY(data->instance));
|
||||||
|
os_free_aligned(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
instance_data_unmap_physical_devices(
|
||||||
|
struct vram_report_limit_instance_data *instance_data)
|
||||||
|
{
|
||||||
|
uint32_t physicalDeviceCount = 0;
|
||||||
|
|
||||||
|
instance_data->vtable.EnumeratePhysicalDevices(instance_data->instance,
|
||||||
|
&physicalDeviceCount, NULL);
|
||||||
|
if (physicalDeviceCount == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkPhysicalDevice *physicalDevices =
|
||||||
|
os_malloc(sizeof(VkPhysicalDevice) * physicalDeviceCount);
|
||||||
|
if (physicalDevices == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance_data->vtable.EnumeratePhysicalDevices(
|
||||||
|
instance_data->instance, &physicalDeviceCount, physicalDevices);
|
||||||
|
assert(physicalDeviceCount > 0);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < physicalDeviceCount; i++) {
|
||||||
|
unmap_object(HKEY(physicalDevices[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
os_free(physicalDevices);
|
||||||
|
}
|
||||||
|
|
||||||
|
static VkLayerInstanceCreateInfo *
|
||||||
|
get_instance_chain_info(const VkInstanceCreateInfo *pCreateInfo)
|
||||||
|
{
|
||||||
|
vk_foreach_struct_const (item, pCreateInfo->pNext) {
|
||||||
|
if (item->sType == VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO &&
|
||||||
|
((VkLayerInstanceCreateInfo *)item)->function == VK_LAYER_LINK_INFO)
|
||||||
|
return (VkLayerInstanceCreateInfo *)item;
|
||||||
|
}
|
||||||
|
unreachable("instance chain info not found");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static VkResult
|
||||||
|
vram_report_limit_CreateInstance(const VkInstanceCreateInfo *pCreateInfo,
|
||||||
|
const VkAllocationCallbacks *pAllocator,
|
||||||
|
VkInstance *pInstance)
|
||||||
|
{
|
||||||
|
VkResult result;
|
||||||
|
|
||||||
|
VkLayerInstanceCreateInfo *chain_info = get_instance_chain_info(pCreateInfo);
|
||||||
|
|
||||||
|
assert(chain_info->u.pLayerInfo);
|
||||||
|
PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr =
|
||||||
|
chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr;
|
||||||
|
|
||||||
|
#define DEFINE_VK_VOID_FUNC_PTR(proc_addr_func, instance, func_name) \
|
||||||
|
CONCAT2(PFN_vk, func_name) \
|
||||||
|
CONCAT2(fp, func_name) = \
|
||||||
|
(CONCAT2(PFN_vk, func_name))proc_addr_func(instance, "vk" #func_name)
|
||||||
|
|
||||||
|
DEFINE_VK_VOID_FUNC_PTR(fpGetInstanceProcAddr, NULL, CreateInstance);
|
||||||
|
if (fpCreateInstance == NULL) {
|
||||||
|
result = VK_ERROR_INITIALIZATION_FAILED;
|
||||||
|
goto err_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PFN_GetPhysicalDeviceProcAddr fpGetPhysicalDeviceProcAddr =
|
||||||
|
chain_info->u.pLayerInfo->pfnNextGetPhysicalDeviceProcAddr;
|
||||||
|
if (fpGetPhysicalDeviceProcAddr == NULL) {
|
||||||
|
result = VK_ERROR_INITIALIZATION_FAILED;
|
||||||
|
goto err_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Advance the link info for the next element on the chain */
|
||||||
|
chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext;
|
||||||
|
|
||||||
|
result = fpCreateInstance(pCreateInfo, pAllocator, pInstance);
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
goto err_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_VK_VOID_FUNC_PTR(fpGetInstanceProcAddr, *pInstance, DestroyInstance);
|
||||||
|
if (fpDestroyInstance == NULL) {
|
||||||
|
result = VK_ERROR_INITIALIZATION_FAILED;
|
||||||
|
goto err_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_VK_VOID_FUNC_PTR(fpGetInstanceProcAddr, *pInstance,
|
||||||
|
EnumeratePhysicalDevices);
|
||||||
|
if (fpEnumeratePhysicalDevices == NULL) {
|
||||||
|
result = VK_ERROR_INITIALIZATION_FAILED;
|
||||||
|
goto err_destroy_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_VK_VOID_FUNC_PTR(fpGetPhysicalDeviceProcAddr, *pInstance,
|
||||||
|
GetPhysicalDeviceProperties);
|
||||||
|
if (fpGetPhysicalDeviceProperties == NULL) {
|
||||||
|
result = VK_ERROR_INITIALIZATION_FAILED;
|
||||||
|
goto err_destroy_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef DEFINE_VK_VOID_FUNC_PTR
|
||||||
|
|
||||||
|
const uint64_t static_heap_size =
|
||||||
|
vram_report_limit_env_get_static_heap_size_or_default();
|
||||||
|
|
||||||
|
VkVendorId vendor_id = ~0;
|
||||||
|
uint32_t device_id = ~0;
|
||||||
|
const bool device_id_is_valid =
|
||||||
|
vram_report_limit_env_get_device_id(&vendor_id, &device_id);
|
||||||
|
|
||||||
|
uint32_t pdevice_count = 0;
|
||||||
|
fpEnumeratePhysicalDevices(*pInstance, &pdevice_count, NULL);
|
||||||
|
|
||||||
|
VkPhysicalDevice *pdevices_array = NULL;
|
||||||
|
bool *is_pdevice_active_array = NULL;
|
||||||
|
if (pdevice_count > 0) {
|
||||||
|
pdevices_array = os_malloc(sizeof(VkPhysicalDevice) * pdevice_count);
|
||||||
|
if (pdevices_array == NULL) {
|
||||||
|
result = VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||||
|
goto err_destroy_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
fpEnumeratePhysicalDevices(*pInstance, &pdevice_count, pdevices_array);
|
||||||
|
|
||||||
|
is_pdevice_active_array =
|
||||||
|
(bool *)os_calloc(pdevice_count, sizeof(*is_pdevice_active_array));
|
||||||
|
if (is_pdevice_active_array == NULL) {
|
||||||
|
result = VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||||
|
goto err_free_pdevices_array;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t active_pdevices_count = 0;
|
||||||
|
if (device_id_is_valid &&
|
||||||
|
static_heap_size != VRAM_REPORT_LIMIT_STATIC_HEAP_SIZE_DEFAULT) {
|
||||||
|
for (uint32_t i = 0; i < pdevice_count; i++) {
|
||||||
|
VkPhysicalDevice pdevice = pdevices_array[i];
|
||||||
|
VkPhysicalDeviceProperties properties;
|
||||||
|
|
||||||
|
is_pdevice_active_array[i] = false;
|
||||||
|
|
||||||
|
fpGetPhysicalDeviceProperties(pdevice, &properties);
|
||||||
|
|
||||||
|
if (properties.vendorID != vendor_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties.deviceID != device_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(DEBUG)
|
||||||
|
printf(VRAM_REPORT_LIMIT_DEBUG_LOG_TAG "Active device: %s\n",
|
||||||
|
properties.deviceName);
|
||||||
|
printf(VRAM_REPORT_LIMIT_DEBUG_LOG_TAG "Static Heap size: %lu MiB\n",
|
||||||
|
static_heap_size / MiB(1));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
is_pdevice_active_array[i] = true;
|
||||||
|
active_pdevices_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active_pdevices_count == 0 &&
|
||||||
|
static_heap_size != VRAM_REPORT_LIMIT_STATIC_HEAP_SIZE_DEFAULT) {
|
||||||
|
fprintf(stderr, VRAM_REPORT_LIMIT_WARN_LOG_TAG
|
||||||
|
"No device found to apply the limit to.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
struct vram_report_limit_instance_data *instance_data = os_malloc_aligned(
|
||||||
|
sizeof(*instance_data) + sizeof(instance_data->active_pdevices_array[0]) *
|
||||||
|
active_pdevices_count,
|
||||||
|
CACHE_LINE_SIZE);
|
||||||
|
if (instance_data == NULL) {
|
||||||
|
result = VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||||
|
goto err_free_is_pdevice_active_array;
|
||||||
|
}
|
||||||
|
|
||||||
|
vk_instance_dispatch_table_load(&instance_data->vtable,
|
||||||
|
fpGetInstanceProcAddr, *pInstance);
|
||||||
|
vk_physical_device_dispatch_table_load(&instance_data->pd_vtable,
|
||||||
|
fpGetInstanceProcAddr, *pInstance);
|
||||||
|
|
||||||
|
instance_data->instance = *pInstance;
|
||||||
|
instance_data->static_heap_size = static_heap_size;
|
||||||
|
|
||||||
|
instance_data->active_pdevices_count = 0;
|
||||||
|
for (uint32_t i = 0; i < pdevice_count; i++) {
|
||||||
|
VkPhysicalDevice pdevice = pdevices_array[i];
|
||||||
|
struct vram_report_limit_pdevice_data *pdevice_data;
|
||||||
|
|
||||||
|
/* Even though multiple physical devices have the same vendor id and
|
||||||
|
* device id, they might not have the same heap arrangements due to
|
||||||
|
* potentially differing drivers. So we have to maintain per pdevice
|
||||||
|
* budged percentages and not just calculate it once to be used with
|
||||||
|
* all.
|
||||||
|
*/
|
||||||
|
if (!is_pdevice_active_array[i]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No device should be active if the default size is set. */
|
||||||
|
assert(static_heap_size != VRAM_REPORT_LIMIT_STATIC_HEAP_SIZE_DEFAULT);
|
||||||
|
|
||||||
|
pdevice_data =
|
||||||
|
&instance_data
|
||||||
|
->active_pdevices_array[instance_data->active_pdevices_count];
|
||||||
|
instance_data->active_pdevices_count++;
|
||||||
|
|
||||||
|
pdevice_data->pdevice = pdevice;
|
||||||
|
|
||||||
|
if (instance_data->pd_vtable.GetPhysicalDeviceMemoryProperties2 == NULL) {
|
||||||
|
#if defined(DEBUG)
|
||||||
|
for (uint32_t i = 0; i < VK_MAX_MEMORY_HEAPS; i++)
|
||||||
|
pdevice_data->per_heap_budget_percentage[i] = NAN;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For each active device we need to setup a budget percentage to scale
|
||||||
|
* down the reported budget to keep it under the new heap size.
|
||||||
|
*/
|
||||||
|
VkPhysicalDeviceMemoryProperties2 memory_properties = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2,
|
||||||
|
};
|
||||||
|
|
||||||
|
instance_data->pd_vtable.GetPhysicalDeviceMemoryProperties2(
|
||||||
|
pdevice, &memory_properties);
|
||||||
|
|
||||||
|
VkMemoryHeap *heaps_array[VK_MAX_MEMORY_HEAPS];
|
||||||
|
uint32_t heaps_array_bitmask;
|
||||||
|
|
||||||
|
vram_report_limit_get_memory_heaps_with_device_property(
|
||||||
|
&memory_properties.memoryProperties, &heaps_array_bitmask,
|
||||||
|
heaps_array);
|
||||||
|
|
||||||
|
STATIC_ASSERT(ARRAY_SIZE(instance_data->active_pdevices_array[0]
|
||||||
|
.per_heap_budget_percentage) ==
|
||||||
|
VK_MAX_MEMORY_HEAPS);
|
||||||
|
for (uint32_t i = 0; i < VK_MAX_MEMORY_HEAPS; i++) {
|
||||||
|
const VkMemoryHeap *const heap = heaps_array[i];
|
||||||
|
|
||||||
|
if (!(BITFIELD_BIT(i) & heaps_array_bitmask)) {
|
||||||
|
pdevice_data->per_heap_budget_percentage[i] = 1.0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(static_heap_size != VRAM_REPORT_LIMIT_STATIC_HEAP_SIZE_DEFAULT);
|
||||||
|
const long double ratio =
|
||||||
|
(long double)instance_data->static_heap_size / heap->size;
|
||||||
|
|
||||||
|
pdevice_data->per_heap_budget_percentage[i] = ratio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map_object(HKEY(instance_data->instance), instance_data);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < pdevice_count; i++) {
|
||||||
|
map_object(HKEY(pdevices_array[i]), instance_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_pdevice_active_array) {
|
||||||
|
os_free(is_pdevice_active_array);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pdevices_array) {
|
||||||
|
os_free(pdevices_array);
|
||||||
|
}
|
||||||
|
|
||||||
|
return VK_SUCCESS;
|
||||||
|
|
||||||
|
err_free_is_pdevice_active_array:
|
||||||
|
if (is_pdevice_active_array) {
|
||||||
|
os_free(is_pdevice_active_array);
|
||||||
|
}
|
||||||
|
|
||||||
|
err_free_pdevices_array:
|
||||||
|
if (pdevices_array) {
|
||||||
|
os_free(pdevices_array);
|
||||||
|
}
|
||||||
|
|
||||||
|
err_destroy_instance:
|
||||||
|
fpDestroyInstance(*pInstance, NULL);
|
||||||
|
|
||||||
|
err_return:
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
vram_report_limit_DestroyInstance(VkInstance instance,
|
||||||
|
const VkAllocationCallbacks *pAllocator)
|
||||||
|
{
|
||||||
|
struct vram_report_limit_instance_data *const instance_data =
|
||||||
|
FIND(struct vram_report_limit_instance_data, instance);
|
||||||
|
|
||||||
|
instance_data_unmap_physical_devices(instance_data);
|
||||||
|
instance_data->vtable.DestroyInstance(instance, pAllocator);
|
||||||
|
|
||||||
|
destroy_instance_data(instance_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
vram_report_limit_apply_budget_percentage(long double percentage,
|
||||||
|
VkDeviceSize *const size_in_out)
|
||||||
|
{
|
||||||
|
const VkDeviceSize old_size = *size_in_out;
|
||||||
|
const VkDeviceSize new_size = (VkDeviceSize)(old_size * percentage);
|
||||||
|
|
||||||
|
#if defined(DEBUG)
|
||||||
|
if (percentage != 1.0) {
|
||||||
|
printf(VRAM_REPORT_LIMIT_DEBUG_LOG_TAG
|
||||||
|
"tweaking budget size to %0.2Lf %%, %" PRIu64 " MiB -> %" PRIu64
|
||||||
|
" MiB\n",
|
||||||
|
percentage * 100, old_size / MiB(1), new_size / MiB(1));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
*size_in_out = new_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (instance_data->static_heap_size ==
|
||||||
|
VRAM_REPORT_LIMIT_STATIC_HEAP_SIZE_DEFAULT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct vram_report_limit_pdevice_data *pdevice_data = NULL;
|
||||||
|
for (uint32_t i = 0; i < instance_data->active_pdevices_count; i++) {
|
||||||
|
if (instance_data->active_pdevices_array[i].pdevice != pdevice) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pdevice_data = &instance_data->active_pdevices_array[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pdevice_data == NULL) {
|
||||||
|
/* The device wasn't selected by the user so don't tweak any values. */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < VK_MAX_MEMORY_HEAPS; i++) {
|
||||||
|
if (i > memory_properties->memoryHeapCount) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(instance_data->static_heap_size !=
|
||||||
|
VRAM_REPORT_LIMIT_STATIC_HEAP_SIZE_DEFAULT);
|
||||||
|
memory_properties->memoryHeaps[i].size = instance_data->static_heap_size;
|
||||||
|
|
||||||
|
if (memory_budget_optional) {
|
||||||
|
const long double percentage =
|
||||||
|
pdevice_data->per_heap_budget_percentage[i];
|
||||||
|
|
||||||
|
vram_report_limit_apply_budget_percentage(
|
||||||
|
percentage, &memory_budget_optional->heapBudget[i]);
|
||||||
|
|
||||||
|
assert(memory_budget_optional->heapBudget[i] <=
|
||||||
|
memory_properties->memoryHeaps[i].size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static VKAPI_ATTR void VKAPI_CALL
|
||||||
|
vram_report_limit_GetPhysicalDeviceMemoryProperties(
|
||||||
|
VkPhysicalDevice physicalDevice,
|
||||||
|
VkPhysicalDeviceMemoryProperties *pMemoryProperties)
|
||||||
|
{
|
||||||
|
struct vram_report_limit_instance_data *instance_data =
|
||||||
|
FIND(struct vram_report_limit_instance_data, physicalDevice);
|
||||||
|
|
||||||
|
instance_data->pd_vtable.GetPhysicalDeviceMemoryProperties(
|
||||||
|
physicalDevice, pMemoryProperties);
|
||||||
|
|
||||||
|
vram_report_limit_tweak_memory_properties(instance_data, physicalDevice,
|
||||||
|
pMemoryProperties, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static VKAPI_ATTR void VKAPI_CALL
|
||||||
|
vram_report_limit_GetPhysicalDeviceMemoryProperties2(
|
||||||
|
VkPhysicalDevice physicalDevice,
|
||||||
|
VkPhysicalDeviceMemoryProperties2 *pMemoryProperties)
|
||||||
|
{
|
||||||
|
struct vram_report_limit_instance_data *instance_data =
|
||||||
|
FIND(struct vram_report_limit_instance_data, physicalDevice);
|
||||||
|
|
||||||
|
instance_data->pd_vtable.GetPhysicalDeviceMemoryProperties2(
|
||||||
|
physicalDevice, pMemoryProperties);
|
||||||
|
|
||||||
|
struct VkPhysicalDeviceMemoryBudgetPropertiesEXT *budget_properties =
|
||||||
|
vk_find_struct(pMemoryProperties->pNext,
|
||||||
|
PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT);
|
||||||
|
|
||||||
|
vram_report_limit_tweak_memory_properties(
|
||||||
|
instance_data, physicalDevice, &pMemoryProperties->memoryProperties,
|
||||||
|
budget_properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
|
||||||
|
vram_report_limit_GetInstanceProcAddr(VkInstance instance,
|
||||||
|
const char *funcName);
|
||||||
|
|
||||||
|
static void *
|
||||||
|
find_ptr(const char *name)
|
||||||
|
{
|
||||||
|
static const struct {
|
||||||
|
const char *name;
|
||||||
|
void *ptr;
|
||||||
|
} name_to_funcptr_map[] = {
|
||||||
|
{"vkGetInstanceProcAddr", (void *)vram_report_limit_GetInstanceProcAddr},
|
||||||
|
#define ADD_HOOK(fn) {"vk" #fn, (void *)vram_report_limit_##fn}
|
||||||
|
#define ADD_ALIAS_HOOK(alias, fn) \
|
||||||
|
{ \
|
||||||
|
"vk" #alias, (void *)vram_report_limit_##fn \
|
||||||
|
}
|
||||||
|
ADD_HOOK(GetPhysicalDeviceMemoryProperties),
|
||||||
|
ADD_HOOK(GetPhysicalDeviceMemoryProperties2),
|
||||||
|
ADD_ALIAS_HOOK(GetPhysicalDeviceMemoryProperties2KHR,
|
||||||
|
GetPhysicalDeviceMemoryProperties2),
|
||||||
|
|
||||||
|
ADD_HOOK(CreateInstance),
|
||||||
|
ADD_HOOK(DestroyInstance),
|
||||||
|
#undef ADD_HOOK
|
||||||
|
#undef ADD_ALIAS_HOOK
|
||||||
|
};
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < ARRAY_SIZE(name_to_funcptr_map); i++) {
|
||||||
|
if (strcmp(name, name_to_funcptr_map[i].name) == 0) {
|
||||||
|
return name_to_funcptr_map[i].ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
|
||||||
|
vram_report_limit_GetInstanceProcAddr(VkInstance instance, const char *funcName)
|
||||||
|
{
|
||||||
|
void *ptr = find_ptr(funcName);
|
||||||
|
if (ptr) {
|
||||||
|
return (PFN_vkVoidFunction)(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instance == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct vram_report_limit_instance_data *instance_data =
|
||||||
|
FIND(struct vram_report_limit_instance_data, instance);
|
||||||
|
if (instance_data->vtable.GetInstanceProcAddr == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance_data->vtable.GetInstanceProcAddr(instance, funcName);
|
||||||
|
}
|
||||||
|
|
||||||
|
PUBLIC VkResult
|
||||||
|
vkNegotiateLoaderLayerInterfaceVersion(VkNegotiateLayerInterface *pVersionStruct)
|
||||||
|
{
|
||||||
|
if (pVersionStruct->loaderLayerInterfaceVersion < 2)
|
||||||
|
return VK_ERROR_INITIALIZATION_FAILED;
|
||||||
|
|
||||||
|
pVersionStruct->loaderLayerInterfaceVersion = 2;
|
||||||
|
pVersionStruct->pfnGetInstanceProcAddr =
|
||||||
|
vram_report_limit_GetInstanceProcAddr;
|
||||||
|
|
||||||
|
return VK_SUCCESS;
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue