Skip to content

Commit

Permalink
Intercept device functions via vkGetInstanceProcAddr() (#22)
Browse files Browse the repository at this point in the history
Vulkan allows device functions to be (less efficiently) intercepted via
vkGetInstanceProcAddr() instead of vkGetDeviceProcAddr(). This PR adds
support for intercepting these correctly.

This includes a custom vkGetDeviceImageMemoryRequirementsKHR() to skip
driver invocations if the driver dispatch pointer is nullptr. This is a
workaround for a bug in Unreal Engine which fetches this function using
vkGetInstanceProcAddr() with a Vulkan 1.1 instance (when it is available
and is therefore non-null) and then tries to use it later with a device
created from a Vulkan 1.0 instance (when it is not available because
VK_KHR_maintenance4 is a Vulkan 1.1 extension).
  • Loading branch information
solidpixel authored Dec 10, 2024
1 parent 683c74d commit 783e42e
Show file tree
Hide file tree
Showing 7 changed files with 524 additions and 92 deletions.
42 changes: 30 additions & 12 deletions generator/generate_vulkan_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def load_api_extensions(self, root):
continue

# Omit extensions that are from other vendors
vendor = ext_name.split("_")[1]
vendor = ext_name.split('_')[1]
if vendor in EXTENSION_VENDOR_FILTER:
continue

Expand Down Expand Up @@ -337,7 +337,7 @@ def generate_layer_instance_dispatch_table(file, mapping, commands):
data = load_template('instance_dispatch_table.txt')

# Create a listing of API versions and API extensions
itable_members = []
itable_members = ['// Instance functions']
dispatch_table_members = []
dispatch_table_inits = []

Expand All @@ -354,7 +354,7 @@ def generate_layer_instance_dispatch_table(file, mapping, commands):
if plat_define:
itable_members.append(f'#if defined({plat_define})')

itable_members.append(f' ENTRY({tname}),')
itable_members.append(f' ENTRY({tname}),')
if plat_define:
itable_members.append('#endif')

Expand All @@ -370,6 +370,24 @@ def generate_layer_instance_dispatch_table(file, mapping, commands):
dispatch_table_members.append('#endif')
dispatch_table_inits.append('#endif')

itable_members = ['\n// Device functions']

for command in commands:
if command.dispatch_type != 'device':
continue

plat_define = mapping.get_platform_define(command.name)
ttype = f'PFN_{command.name}'
tname = command.name

if plat_define:
itable_members.append(f'#if defined({plat_define})')

itable_members.append(f' ENTRY({tname}),')

if plat_define:
itable_members.append('#endif')

data = data.replace('{ITABLE_MEMBERS}', '\n'.join(itable_members))
data = data.replace('{DTABLE_MEMBERS}', '\n'.join(dispatch_table_members))
data = data.replace('{DTABLE_INITS}', '\n'.join(dispatch_table_inits))
Expand All @@ -389,7 +407,7 @@ def generate_layer_instance_layer_decls(file, mapping, commands):

# Create a listing of API versions and API extensions
for command in commands:
if command.dispatch_type != "instance":
if command.dispatch_type != 'instance':
continue

lines = []
Expand Down Expand Up @@ -426,7 +444,7 @@ def generate_layer_instance_layer_decls(file, mapping, commands):
parl = f' {ptype} {pname}{array}{ending}'
lines.append(parl)

parmfwd = ", ".join([x[1] for x in command.params])
parmfwd = ', '.join([x[1] for x in command.params])
retfwd = 'return ' if command.rtype != 'void' else ''
lines.append(') {')
lines.append(f' {retfwd}layer_{command.name}_default({parmfwd});')
Expand All @@ -449,7 +467,7 @@ def generate_layer_instance_layer_defs(file, mapping, commands, manual_commands)
# Create a listing of API versions and API extensions
lines = []
for command in commands:
if command.dispatch_type != "instance":
if command.dispatch_type != 'instance':
continue

plat_define = mapping.get_platform_define(command.name)
Expand All @@ -468,7 +486,7 @@ def generate_layer_instance_layer_defs(file, mapping, commands, manual_commands)
lines.append(parl)

dispatch = command.params[0][1]
parmfwd = ", ".join([x[1] for x in command.params])
parmfwd = ', '.join([x[1] for x in command.params])
retfwd = 'return ' if command.rtype != 'void' else ''

lines.append(') {')
Expand Down Expand Up @@ -506,7 +524,7 @@ def generate_layer_device_dispatch_table(file, mapping, commands):
dispatch_table_members = []
dispatch_table_inits = []
for command in commands:
if command.dispatch_type != "device":
if command.dispatch_type != 'device':
continue

plat_define = mapping.get_platform_define(command.name)
Expand Down Expand Up @@ -546,7 +564,7 @@ def generate_layer_device_layer_decls(file, mapping, commands):

# Create a listing of API versions and API extensions
for command in commands:
if command.dispatch_type != "device":
if command.dispatch_type != 'device':
continue

lines = []
Expand Down Expand Up @@ -582,7 +600,7 @@ def generate_layer_device_layer_decls(file, mapping, commands):
parl = f' {ptype} {pname}{array}{ending}'
lines.append(parl)

parmfwd = ", ".join([x[1] for x in command.params])
parmfwd = ', '.join([x[1] for x in command.params])
retfwd = 'return ' if command.rtype != 'void' else ''
lines.append(') {')
lines.append(f' {retfwd}layer_{command.name}_default({parmfwd});')
Expand All @@ -605,7 +623,7 @@ def generate_layer_device_layer_defs(file, mapping, commands, manual_commands):
# Create a listing of API versions and API extensions
lines = []
for command in commands:
if command.dispatch_type != "device":
if command.dispatch_type != 'device':
continue

plat_define = mapping.get_platform_define(command.name)
Expand All @@ -625,7 +643,7 @@ def generate_layer_device_layer_defs(file, mapping, commands, manual_commands):
lines.append(parl)

dispatch = command.params[0][1]
parmfwd = ", ".join([x[1] for x in command.params])
parmfwd = ', '.join([x[1] for x in command.params])
retfwd = 'return ' if command.rtype != 'void' else ''

lines.append(') {')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Hold the lock to access layer-wide global store
std::unique_lock<std::mutex> lock { g_vulkanLock };
auto* layer = Device::retrieve(device);

// Workaround Unreal Engine trying to invoke this via a function pointer
// queried from a Vulkan 1.1 instance with vkInstanceGetProcAddress() with
// device created from a later a Vulkan 1.0 context where the function is
// not available ...
if (!layer->driver.vkGetDeviceImageMemoryRequirementsKHR)
{
pMemoryRequirements->memoryRequirements.size = 0;
pMemoryRequirements->memoryRequirements.alignment = 0;
pMemoryRequirements->memoryRequirements.memoryTypeBits = 0;
return;
}

// Release the lock to call into the driver
lock.unlock();
layer->driver.vkGetDeviceImageMemoryRequirementsKHR(device, pInfo, pMemoryRequirements);
1 change: 1 addition & 0 deletions generator/vk_codegen/instance_dispatch_table.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <vulkan/vulkan.h>

#include "framework/device_functions.hpp"
#include "framework/instance_functions.hpp"
#include "framework/utils.hpp"
#include "utils/misc.hpp"
Expand Down
12 changes: 12 additions & 0 deletions source_common/framework/device_functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5959,6 +5959,18 @@ VKAPI_ATTR void VKAPI_CALL layer_vkGetDeviceImageMemoryRequirementsKHR_default(
std::unique_lock<std::mutex> lock { g_vulkanLock };
auto* layer = Device::retrieve(device);

// Workaround Unreal Engine trying to invoke this via a function pointer
// queried from a Vulkan 1.1 instance with vkInstanceGetProcAddress() with
// device created from a later a Vulkan 1.0 context where the function is
// not available ...
if (!layer->driver.vkGetDeviceImageMemoryRequirementsKHR)
{
pMemoryRequirements->memoryRequirements.size = 0;
pMemoryRequirements->memoryRequirements.alignment = 0;
pMemoryRequirements->memoryRequirements.memoryTypeBits = 0;
return;
}

// Release the lock to call into the driver
lock.unlock();
layer->driver.vkGetDeviceImageMemoryRequirementsKHR(device, pInfo, pMemoryRequirements);
Expand Down
Loading

0 comments on commit 783e42e

Please sign in to comment.