From 4ff1c7d2c8a7581602188df922edb491c6e9401e Mon Sep 17 00:00:00 2001 From: manon-traverse Date: Mon, 17 Aug 2026 10:50:02 +0200 Subject: [PATCH] vulkan: expose promoted core functions based on the physical device version Apps may request a low VkApplicationInfo::apiVersion yet still call core functions that were promoted in a later version, relying on the driver providing them anyway (common on Android/Adreno). RenderDoc gated promoted function exposure on the app-requested version (RDCMIN of the requested and physical-device versions), so GetDeviceProcAddr returned NULL for those functions and the app crashed calling a null pointer; replay hit the same gating. Gate exposure on the physical device's supported apiVersion instead, matching the driver's actual behaviour - in vkCreateInstance, vkCreateDevice and Serialise_vkCreateDevice. Co-Authored-By: Claude Opus 4.8 --- .../vulkan/wrappers/vk_device_funcs.cpp | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/renderdoc/driver/vulkan/wrappers/vk_device_funcs.cpp b/renderdoc/driver/vulkan/wrappers/vk_device_funcs.cpp index c65982294c5..8f52b478ad1 100644 --- a/renderdoc/driver/vulkan/wrappers/vk_device_funcs.cpp +++ b/renderdoc/driver/vulkan/wrappers/vk_device_funcs.cpp @@ -855,6 +855,11 @@ VkResult WrappedVulkan::vkCreateInstance(const VkInstanceCreateInfo *pCreateInfo record->instDevInfo->vulkanVersion = renderdocAppInfo.apiVersion; std::set availablePhysDeviceFunctions; + // Track the highest core apiVersion actually supported by a physical device. Some apps (e.g. + // Adreno titles) request a low VkApplicationInfo::apiVersion but still call promoted core + // functions that the driver leniently provides. Gate promoted-function exposure on this too, + // otherwise GetDeviceProcAddr returns NULL for them and the app crashes calling a null pointer. + uint32_t maxPhysDeviceApiVersion = 0; { uint32_t count = 0; @@ -867,6 +872,10 @@ VkResult WrappedVulkan::vkCreateInstance(const VkInstanceCreateInfo *pCreateInfo rdcarray exts; for(VkPhysicalDevice p : physDevs) { + VkPhysicalDeviceProperties physProps = {}; + ObjDisp(m_Instance)->GetPhysicalDeviceProperties(p, &physProps); + maxPhysDeviceApiVersion = RDCMAX(maxPhysDeviceApiVersion, physProps.apiVersion); + ObjDisp(m_Instance)->EnumerateDeviceExtensionProperties(p, NULL, &count, NULL); exts.resize(count); @@ -901,7 +910,7 @@ VkResult WrappedVulkan::vkCreateInstance(const VkInstanceCreateInfo *pCreateInfo // * it's a device extension and available on at least one physical device #undef CheckExt #define CheckExt(name, ver) \ - if(record->instDevInfo->vulkanVersion >= ver || \ + if(record->instDevInfo->vulkanVersion >= ver || maxPhysDeviceApiVersion >= ver || \ availablePhysDeviceFunctions.find("VK_" #name) != availablePhysDeviceFunctions.end()) \ { \ record->instDevInfo->ext_##name = true; \ @@ -1935,7 +1944,7 @@ bool WrappedVulkan::Serialise_vkCreateDevice(SerialiserType &ser, VkPhysicalDevi // enable VK_KHR_shader_subgroup_uniform_control_flow if it's available, to make subgroup // debugging more reliable/spec-clean. // if we can't get it, we'll just emit the same code anyway and hope it compiles to something sensible - if(RDCMIN(m_EnabledExtensions.vulkanVersion, physProps.apiVersion) >= VK_MAKE_VERSION(1, 1, 0)) + if(physProps.apiVersion >= VK_MAKE_VERSION(1, 1, 0)) { if(supportedExtensions.find(VK_KHR_SHADER_SUBGROUP_UNIFORM_CONTROL_FLOW_EXTENSION_NAME) != supportedExtensions.end()) @@ -4098,7 +4107,7 @@ bool WrappedVulkan::Serialise_vkCreateDevice(SerialiserType &ser, VkPhysicalDevi VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCALAR_BLOCK_LAYOUT_FEATURES, }; - if(RDCMIN(m_EnabledExtensions.vulkanVersion, physProps.apiVersion) >= VK_MAKE_VERSION(1, 2, 0)) + if(physProps.apiVersion >= VK_MAKE_VERSION(1, 2, 0)) { VkPhysicalDeviceVulkan12Features avail12Features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, @@ -4186,7 +4195,7 @@ bool WrappedVulkan::Serialise_vkCreateDevice(SerialiserType &ser, VkPhysicalDevi VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES, }; - if(RDCMIN(m_EnabledExtensions.vulkanVersion, physProps.apiVersion) >= VK_MAKE_VERSION(1, 3, 0)) + if(physProps.apiVersion >= VK_MAKE_VERSION(1, 3, 0)) { // VK_EXT_extended_dynamic_state and VK_EXT_extended_dynamic_state2 were unconditionally // promoted and considered implicitly enabled in vulkan 1.3 @@ -4195,7 +4204,7 @@ bool WrappedVulkan::Serialise_vkCreateDevice(SerialiserType &ser, VkPhysicalDevi // logic and patch CPs were not } - if(RDCMIN(m_EnabledExtensions.vulkanVersion, physProps.apiVersion) >= VK_MAKE_VERSION(1, 2, 0)) + if(physProps.apiVersion >= VK_MAKE_VERSION(1, 2, 0)) { VkPhysicalDeviceVulkan12Features avail12Features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, @@ -4405,7 +4414,7 @@ bool WrappedVulkan::Serialise_vkCreateDevice(SerialiserType &ser, VkPhysicalDevi CheckDeviceExts(); - uint32_t effectiveApiVersion = RDCMIN(m_EnabledExtensions.vulkanVersion, physProps.apiVersion); + uint32_t effectiveApiVersion = physProps.apiVersion; #undef CheckExt #define CheckExt(name, ver) \ @@ -4669,7 +4678,7 @@ bool WrappedVulkan::Serialise_vkCreateDevice(SerialiserType &ser, VkPhysicalDevi for(size_t i = 0; i < queueProps.size(); i++) m_PhysicalDeviceData.queueProps[i] = queueProps[i]; - if(RDCMIN(m_EnabledExtensions.vulkanVersion, physProps.apiVersion) >= VK_MAKE_VERSION(1, 1, 0)) + if(physProps.apiVersion >= VK_MAKE_VERSION(1, 1, 0)) { VkPhysicalDeviceVulkan11Properties vulkan11Props = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES, @@ -5130,8 +5139,12 @@ VkResult WrappedVulkan::vkCreateDevice(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties physProps; ObjDisp(physicalDevice)->GetPhysicalDeviceProperties(Unwrap(physicalDevice), &physProps); - record->instDevInfo->vulkanVersion = - RDCMIN(physProps.apiVersion, GetRecord(m_Instance)->instDevInfo->vulkanVersion); + // Use the physical device's supported apiVersion rather than RDCMIN with the app-requested + // version. Some apps (e.g. Adreno titles like Asphalt 9) request a low VkApplicationInfo + // apiVersion but still call promoted core functions that the driver leniently provides; + // taking the MIN made GetDeviceProcAddr return NULL for those, and the app crashed calling + // a null pointer. Exposing everything the physical device supports matches the driver. + record->instDevInfo->vulkanVersion = physProps.apiVersion; #undef CheckExt #define CheckExt(name, ver) \