KhronosGroup / KhronosGroup/Vulkan-ValidationLayers

Track if vkGetDeviceProcAddr handle matches the call

Open
#12,153 0 comments 0 reactions 0 assignees View on GitHub
Incomplete
Dominant language
C++
Stars
1k
Forks
504
Avg merge
11h 35m
Merged PRs (30d)
224

Description

For `vkGetDeviceProcAddr` it says

Image

But with a simple tests, it shows that we are not validating this such that if you call

```c++
auto pfnCreateBufferA = (PFN_vkCreateBuffer)vkGetDeviceProcAddr(deviceA, "vkCreateBuffer");
auto pfnCreateBufferB = (PFN_vkCreateBuffer)vkGetDeviceProcAddr(deviceB, "vkCreateBuffer");

pfnCreateBufferA(deviceB, &bufferInfo, nullptr, &bufferB);
pfnCreateBufferB(deviceA, &bufferInfo, nullptr, &bufferA);
```

It will not call an error and "just works" because you might be getting the same pointer back from `vkGetDeviceProcAddr`

Full test case

```c++
// g++ test.cpp -o vulkan_swap_test -lvulkan -I $VULKAN_SDK/include -L $VULKAN_SDK/lib

#include
#include
#include

// Simple macro to check Vulkan results
#define VK_CHECK(result) \
if (result != VK_SUCCESS) { \
throw std::runtime_error("Vulkan API call failed!"); \
}

int main() {
try {
// 1. Create Vulkan Instance
VkApplicationInfo appInfo{};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = "Device Pointer Swap Test";
appInfo.apiVersion = VK_API_VERSION_1_0;

VkInstanceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;

VkInstance instance;
VK_CHECK(vkCreateInstance(&createInfo, nullptr, &instance));

// 2. Get a Physical Device
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
if (deviceCount == 0) throw std::runtime_error("No Vulkan physical devices found.");

std::vector physicalDevices(deviceCount);
vkEnumeratePhysicalDevices(instance, &deviceCount, physicalDevices.data());
VkPhysicalDevice physicalDevice = physicalDevices[0];
std::cout << "Using Physical Device 0.\n";

// 3. Find a queue family (we just need one to create the device)
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr);
std::vector queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilies.data());

uint32_t queueFamilyIndex = 0; // Just use the first available queue family

// 4. Create Device A and Device B
float queuePriority = 1.0f;
VkDeviceQueueCreateInfo queueCreateInfo{};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = queueFamilyIndex;
queueCreateInfo.queueCount = 1;
queueCreateInfo.pQueuePriorities = &queuePriority;

VkDeviceCreateInfo deviceCreateInfo{};
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo;
deviceCreateInfo.queueCreateInfoCount = 1;

VkDevice deviceA, deviceB;
VK_CHECK(vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &deviceA));
std::cout << "Device A created 0x" << std::hex << deviceA << "\n";
VK_CHECK(vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &deviceB));
std::cout << "Device B created 0x" << std::hex << deviceB << "\n";

// 5. Get Device-specific function pointers
auto pfnCreateBufferA = (PFN_vkCreateBuffer)vkGetDeviceProcAddr(deviceA, "vkCreateBuffer");
auto pfnCreateBufferB = (PFN_vkCreateBuffer)vkGetDeviceProcAddr(deviceB, "vkCreateBuffer");

if (!pfnCreateBufferA || !pfnCreateBufferB) {
throw std::runtime_error("Failed to get vkCreateBuffer function pointers!");
}
std::cout << "Loaded pfnCreateBufferA: " << (void*)pfnCreateBufferA << "\n";
std::cout << "Loaded pfnCreateBufferB: " << (void*)pfnCreateBufferB << "\n";

// 6. Setup Buffer Creation Info
VkBufferCreateInfo bufferInfo{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = 1024;
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

VkBuffer bufferA, bufferB;

// Use Function Pointer A with Device Handle B
std::cout << "Calling pfnCreateBufferA with deviceB handle...\n";
VkResult res1 = pfnCreateBufferA(deviceB, &bufferInfo, nullptr, &bufferB);

// Use Function Pointer B with Device Handle A
std::cout << "Calling pfnCreateBufferB with deviceA handle...\n";
VkResult res2 = pfnCreateBufferB(deviceA, &bufferInfo, nullptr, &bufferA);

// 8. Cleanup
// We load the destroy buffer pointers just to be thorough and safe
auto pfnDestroyBufferA = (PFN_vkDestroyBuffer)vkGetDeviceProcAddr(deviceA, "vkDestroyBuffer");
auto pfnDestroyBufferB = (PFN_vkDestroyBuffer)vkGetDeviceProcAddr(deviceB, "vkDestroyBuffer");

if (res2 == VK_SUCCESS) pfnDestroyBufferA(deviceA, bufferA, nullptr);
if (res1 == VK_SUCCESS) pfnDestroyBufferB(deviceB, bufferB, nullptr);

vkDestroyDevice(deviceA, nullptr);
vkDestroyDevice(deviceB, nullptr);
vkDestroyInstance(instance, nullptr);

} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
return 1;
}

return 0;
}
```

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the reported behavior with the provided C++ test case and inspect validation around vkGetDeviceProcAddr and the subsequent vkCreateBuffer calls. Determine whether the returned function pointer is associated with the device handle used at invocation, then add coverage showing the expected result for mismatched handles.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
testing-qa
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.