Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add vk_function_loader.hpp #152

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.19")
vulkan/vk_enum_string_helper.h
vulkan/utility/vk_format_utils.h
vulkan/utility/vk_struct_helper.hpp
vulkan/utility/experimental/vk_function_loader.hpp
)
endif()

target_link_Libraries(VulkanUtilityHeaders INTERFACE Vulkan::Headers)
target_link_Libraries(VulkanUtilityHeaders INTERFACE Vulkan::Headers ${CMAKE_DL_LIBS})

target_include_directories(VulkanUtilityHeaders INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)

6,366 changes: 6,366 additions & 0 deletions include/vulkan/utility/experimental/vk_function_loader.hpp

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions scripts/generate_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def RunGenerators(api: str, registry: str, targetFilter: str) -> None:
from generators.enum_string_helper_generator import EnumStringHelperOutputGenerator
from generators.format_utils_generator import FormatUtilsOutputGenerator
from generators.struct_helper_generator import StructHelperOutputGenerator
from generators.function_loader_generator import FunctionLoaderOutputGenerator

# These set fields that are needed by both OutputGenerator and BaseGenerator,
# but are uniform and don't need to be set at a per-generated file level
Expand Down Expand Up @@ -54,6 +55,11 @@ def RunGenerators(api: str, registry: str, targetFilter: str) -> None:
'genCombined': True,
'directory' : f'include/{api}/utility',
},
'vk_function_loader.hpp' : {
'generator' : FunctionLoaderOutputGenerator,
'genCombined': True,
'directory' : f'include/{api}/utility/experimental',
},
}

if (targetFilter and targetFilter not in generators.keys()):
Expand Down
229 changes: 229 additions & 0 deletions scripts/generators/function_loader_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
#!/usr/bin/python3 -i
#
# Copyright 2023 The Khronos Group Inc.
# Copyright 2023 Valve Corporation
# Copyright 2023 LunarG, Inc.
#
# SPDX-License-Identifier: Apache-2.0

import os
from generators.base_generator import BaseGenerator
from generators.generator_utils import PlatformGuardHelper

class FunctionLoaderOutputGenerator(BaseGenerator):
def __init__(self):
BaseGenerator.__init__(self)

def print_global_function_table(self):
out = []
out.append('''struct VkuGlobalFunctionTable {
detail::vku_dl_handle lib_handle{};
PFN_vkGetInstanceProcAddr GetInstanceProcAddr{};
''')
global_function_names = ['vkEnumerateInstanceVersion', 'vkEnumerateInstanceExtensionProperties', 'vkEnumerateInstanceLayerProperties', 'vkCreateInstance']
for command in global_function_names:
out.append(f' PFN_{command} {command[2:]}{{}};\n')
out.append(' VkResult init(PFN_vkGetInstanceProcAddr optional_vkGetInstanceProcAddr = nullptr) {\n')
out.append(' if (nullptr != optional_vkGetInstanceProcAddr) {\n')
out.append(' GetInstanceProcAddr = optional_vkGetInstanceProcAddr;\n')
for command in global_function_names:
out.append(f' {command[2:]} = reinterpret_cast<PFN_{command}>(optional_vkGetInstanceProcAddr(nullptr, "{command}"));\n')
out.append(''' return VK_SUCCESS;
}
#if(WIN32)
const char filename[] = "vulkan-1.dll";
lib_handle = detail::vkuOpenLibrary(filename);
#elif(__APPLE__)
char filename[] = "libvulkan.dylib";
lib_handle = detail::vkuOpenLibrary(filename);
#else
const char *filename = "libvulkan.so";
lib_handle = detail::vkuOpenLibrary(filename);
if (!lib_handle) {
filename = "libvulkan.so.1";
lib_handle = detail::vkuOpenLibrary(filename);
}
#endif

if (lib_handle == nullptr) {
printf("%s\\n", detail::vkuOpenLibraryError(filename));
return VK_ERROR_INITIALIZATION_FAILED;
}

GetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(detail::vkuGetProcAddress(lib_handle, "vkGetInstanceProcAddr"));
if (GetInstanceProcAddr == nullptr){
return VK_ERROR_INITIALIZATION_FAILED;
}
''')

for command in global_function_names:
out.append(f' {command[2:]} = reinterpret_cast<PFN_{command}>(detail::vkuGetProcAddress(lib_handle, "{command}"));\n')
out.append(' return VK_SUCCESS;\n')
out.append(' }\n')
out.append(''' void release() {
if (lib_handle != nullptr) {
detail::vkuCloseLibrary(lib_handle);
}
}
''')
out.append('};\n')

return out

def check_if_should_print(self, command, allowed_dispatch_types, dispatch_type):
if command.params[0].type not in allowed_dispatch_types:
# must explicitely include vkGetDeviceProcAddr in the VkInstance table
if not(dispatch_type == 'VkInstance' and command.name == 'vkGetDeviceProcAddr'):
return True
return False

def print_dispatch_table(self, dispatch_type, dispatch_name):
out = []
out.append(f'struct Vku{dispatch_type[2:]}FunctionTable {{\n')
out.append(f' {dispatch_type} {dispatch_name};\n')

allowed_dispatch_types = [dispatch_type]
if dispatch_type == 'VkInstance':
allowed_dispatch_types.append('VkPhysicalDevice')
if dispatch_type == 'VkDevice':
allowed_dispatch_types.append('VkCommandBuffer')
allowed_dispatch_types.append('VkQueue')


guard_helper = PlatformGuardHelper()
for command in self.vk.commands.values():
if self.check_if_should_print(command, allowed_dispatch_types, dispatch_type):
continue
out.extend(guard_helper.addGuard(command.protect))
out.append(f' PFN_{command.name} pfn_{command.name[2:]}{{}};\n')
out.extend(guard_helper.addGuard(None))

for command in self.vk.commands.values():
if command.params[0].type not in allowed_dispatch_types:
continue
out.extend(guard_helper.addGuard(command.protect))
out.append(f' {command.returnType} {command.name[2:]}(\n')
modified_param_list = command.params
if modified_param_list[0].type == dispatch_type:
modified_param_list = modified_param_list[1:]
param_decls = []
for param in modified_param_list:
# split & join to trim out whitespace in the cDeclaration
param_decls.append(' '.join(param.cDeclaration.split(' ')))
out.append(f' {",".join(param_decls)}')
return_stmt = 'return ' if command.returnType != 'void' else ''
out.append(f') {{\n {return_stmt}pfn_{command.name[2:]}(')
param_names = []
for param in command.params:
param_names.append(param.name)
out.append(f'{", ".join(param_names)}')
out.append(');\n }\n')

out.extend(guard_helper.addGuard(None))

gpa = 'PFN_vkGetInstanceProcAddr gpa' if dispatch_type in ['VkInstance', 'VkPhysicalDevice'] else 'PFN_vkGetDeviceProcAddr gpa'
gpa_dispatch_decl = 'VkInstance in_instance' if dispatch_type in ['VkInstance', 'VkPhysicalDevice'] else 'VkDevice in_device'
gpa_dispatch = 'in_instance' if dispatch_type in ['VkInstance', 'VkPhysicalDevice'] else 'in_device'
need_explicit_dispatch = ''
if dispatch_type in ['VkPhysicalDevice']:
need_explicit_dispatch = ', VkPhysicalDevice in_physicalDevice'
elif dispatch_type in ['VkCommandBuffer']:
need_explicit_dispatch = ', VkCommandBuffer in_commandBuffer'
elif dispatch_type in ['VkCommandBuffer', 'VkQueue']:
need_explicit_dispatch = ', VkQueue in_queue'
out.append(f' VkResult init({gpa}, {gpa_dispatch_decl}{need_explicit_dispatch}) {{\n')
out.append(f' this->{dispatch_name} = in_{dispatch_name};\n')

for command in self.vk.commands.values():
if self.check_if_should_print(command, allowed_dispatch_types, dispatch_type):
continue
out.extend(guard_helper.addGuard(command.protect))
out.append(f' pfn_{command.name[2:]} = (PFN_{command.name})gpa({gpa_dispatch}, "{command.name}");\n')
out.extend(guard_helper.addGuard(None))
out.append(' return VK_SUCCESS;\n')
out.append(' }\n')
out.append('};\n')

return out

def generate(self):
out = []
out.append(f'''// *** THIS FILE IS GENERATED - DO NOT EDIT ***
// See {os.path.basename(__file__)} for modifications
// Copyright 2023 The Khronos Group Inc.
// Copyright 2023 Valve Corporation
// Copyright 2023 LunarG, Inc.
//
// SPDX-License-Identifier: Apache-2.0
''')

out.append('''
#pragma once

#include <vulkan/vulkan.h>

#ifdef _WIN32
namespace detail {
// Dynamic Loading:
typedef HMODULE vku_dl_handle;
static vku_dl_handle vkuOpenLibrary(const char *lib_path) {
// Try loading the library the original way first.
vku_dl_handle lib_handle = LoadLibrary(lib_path);
if (lib_handle == NULL && GetLastError() == ERROR_MOD_NOT_FOUND) {
// If that failed, then try loading it with broader search folders.
lib_handle = LoadLibraryEx(lib_path, NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR);
}
return lib_handle;
}
static char *vkuOpenLibraryError(const char *libPath) {
static char errorMsg[164];
(void)snprintf(errorMsg, 163, "Failed to open dynamic library \\"%s\\" with error %lu", libPath, GetLastError());
return errorMsg;
}
static void *vkuGetProcAddress(vku_dl_handle library, const char *name) {
assert(library);
assert(name);
return (void *)GetProcAddress(library, name);
}
static inline void vkuCloseLibrary(vku_dl_handle library) {
FreeLibrary(library);
}
} // namespace detail
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__)

#include <dlfcn.h>
namespace detail {

typedef void *vku_dl_handle;
static inline vku_dl_handle vkuOpenLibrary(const char *libPath) {
// When loading the library, we use RTLD_LAZY so that not all symbols have to be
// resolved at this time (which improves performance). Note that if not all symbols
// can be resolved, this could cause crashes later. Use the LD_BIND_NOW environment
// variable to force all symbols to be resolved here.
return dlopen(libPath, RTLD_LAZY | RTLD_LOCAL);
}
static inline const char *vkuOpenLibraryError([[maybe_unused]] const char *libPath) { return dlerror(); }
static inline void *vkuGetProcAddress(vku_dl_handle library, const char *name) {
assert(library);
assert(name);
return dlsym(library, name);
}
static inline void vkuCloseLibrary(vku_dl_handle library) {
dlclose(library);
}
} // namespace detail
#else
#error Dynamic library functions must be defined for this OS.
#endif

''')
out.extend(self.print_global_function_table())
out.extend(self.print_dispatch_table('VkInstance', 'instance'))
out.extend(self.print_dispatch_table('VkPhysicalDevice', 'physicalDevice'))
out.extend(self.print_dispatch_table('VkDevice', 'device'))
out.extend(self.print_dispatch_table('VkCommandBuffer', 'commandBuffer'))
out.extend(self.print_dispatch_table('VkQueue', 'queue'))


self.write("".join(out))

1 change: 1 addition & 0 deletions scripts/gn/stub.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@
#include <vulkan/utility/vk_dispatch_table.h>
#include <vulkan/utility/vk_format_utils.h>
#include <vulkan/utility/vk_struct_helper.hpp>
#include <vulkan/utility/experimental/vk_function_loader.hpp>
#include <vulkan/vk_enum_string_helper.h>
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ target_include_directories(vul_tests PRIVATE
target_sources(vul_tests PRIVATE
struct_helper.cpp
test_formats.cpp
test_function_loader.cpp
test_interface.cpp
test_setting_api.cpp
test_setting_cast.cpp
Expand Down
63 changes: 63 additions & 0 deletions tests/test_function_loader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2023 The Khronos Group Inc.
// Copyright 2023 Valve Corporation
// Copyright 2023 LunarG, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//

#include <gtest/gtest.h>

#include <vulkan/utility/experimental/vk_function_loader.hpp>

// Only exists so that local_vkGetInstance/DeviceProcAddr can return a 'real' function pointer
VKAPI_ATTR void empty_func() {}

VKAPI_ATTR PFN_vkVoidFunction local_vkGetDeviceProcAddr([[maybe_unused]] VkDevice device, const char *pName) {
if (strcmp(pName, "vkGetDeviceProcAddr")) {
return reinterpret_cast<PFN_vkVoidFunction>(&local_vkGetDeviceProcAddr);
}

return reinterpret_cast<PFN_vkVoidFunction>(&empty_func);
}

VKAPI_ATTR PFN_vkVoidFunction local_vkGetInstanceProcAddr([[maybe_unused]] VkInstance instance, const char *pName) {
if (strcmp(pName, "vkGetInstanceProcAddr")) {
return reinterpret_cast<PFN_vkVoidFunction>(&local_vkGetInstanceProcAddr);
}
if (strcmp(pName, "vkGetDeviceProcAddr")) {
return reinterpret_cast<PFN_vkVoidFunction>(&local_vkGetDeviceProcAddr);
}
return reinterpret_cast<PFN_vkVoidFunction>(&empty_func);
}

TEST(test_vk_function_loader, Call_Init) {
VkuGlobalFunctionTable gft;
ASSERT_EQ(VK_SUCCESS, gft.init(local_vkGetInstanceProcAddr));
ASSERT_NE(nullptr, gft.EnumerateInstanceExtensionProperties);
ASSERT_NE(nullptr, gft.EnumerateInstanceLayerProperties);
ASSERT_NE(nullptr, gft.EnumerateInstanceVersion);
ASSERT_NE(nullptr, gft.CreateInstance);

VkuInstanceFunctionTable ift;
ASSERT_EQ(VK_SUCCESS, ift.init(gft.GetInstanceProcAddr, {}));
ASSERT_NE(nullptr, ift.pfn_CreateDevice);
ASSERT_NE(nullptr, ift.pfn_EnumeratePhysicalDeviceGroups);

VkuPhysicalDeviceFunctionTable pdft;
pdft.init(ift.pfn_GetInstanceProcAddr, ift.instance, {});
ASSERT_NE(nullptr, ift.pfn_GetPhysicalDeviceProperties);

VkuDeviceFunctionTable dft;
dft.init(ift.pfn_GetDeviceProcAddr, {});
ASSERT_NE(nullptr, dft.pfn_DestroyDevice);

VkuCommandBufferFunctionTable cbft;
cbft.init(dft.pfn_GetDeviceProcAddr, dft.device, {});
ASSERT_NE(nullptr, cbft.pfn_CmdBindVertexBuffers);

VkuQueueFunctionTable qft;
qft.init(dft.pfn_GetDeviceProcAddr, dft.device, {});
ASSERT_NE(nullptr, qft.pfn_QueueSubmit);

gft.release();
}
6 changes: 3 additions & 3 deletions tests/test_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
#include <vulkan/utility/vk_dispatch_table.h>

// Only exists so that local_vkGetDeviceProcAddr can return a 'real' function pointer
inline VKAPI_ATTR void empty_func() {}
static VKAPI_ATTR void empty_func() {}

inline VKAPI_ATTR PFN_vkVoidFunction local_vkGetInstanceProcAddr(VkInstance instance, const char *pName) {
static VKAPI_ATTR PFN_vkVoidFunction local_vkGetInstanceProcAddr(VkInstance instance, const char *pName) {
if (instance == VK_NULL_HANDLE) {
return NULL;
}
Expand All @@ -24,7 +24,7 @@ inline VKAPI_ATTR PFN_vkVoidFunction local_vkGetInstanceProcAddr(VkInstance inst
return reinterpret_cast<PFN_vkVoidFunction>(&empty_func);
}

inline VKAPI_ATTR PFN_vkVoidFunction local_vkGetDeviceProcAddr(VkDevice device, const char *pName) {
static VKAPI_ATTR PFN_vkVoidFunction local_vkGetDeviceProcAddr(VkDevice device, const char *pName) {
if (device == VK_NULL_HANDLE) {
return NULL;
}
Expand Down