Skip to content

Layer interface version negotiation does not work as expected #1904

Description

@PancakeTAS

Describe the bug

During Vulkan layer version negotiation, if the layer does not support an interface version, it will return VK_ERROR_INITIALIZATION_FAILED during the call to vkNegotiateLoaderLayerInterfaceVersion

If the layer receiving the call no longer supports the interface version provided by the loader (due to deprecation), then it should report a VK_ERROR_INITIALIZATION_FAILED error. Otherwise it sets the value pointed by "loaderLayerInterfaceVersion" to the latest interface version supported by both the layer and the loader and returns VK_SUCCESS. [Source]dead code

Furthermore, when VK_ERROR_INITIALIZATION_FAILED is returned, the loader should not load the layer.

If the loader receives VK_ERROR_INITIALIZATION_FAILED instead of VK_SUCCESS, then the loader will treat the layer as unusable and will not load it. In this case, the application will not see the layer during enumeration. Note that the loader is currently backwards compatible with all layer interface versions, so a layer should not be able to request a version older than what the loader supports. [Source]

This is not what the loader does. Instead, it tries to fallback to finding vkGetInstanceProcAddr directly in the shared library.

The layer negotiation function is called in loader_get_layer_interface_version(). The comments here correctly indicate, that if negotiation fails, the loader should fail loading the Layer.

if (result != VK_SUCCESS) {
    // Layer no longer supports the loader's latest interface version so
    // fail loading the Layer
    return false;
}

This function is called for each activated layer in loader_create_instance_chain(). If the negotiation interface is found and returns success, the interface version and vkGet*ProcAddr functions are updated (there is actually a useless check in loader_get_layer_interface_version, because negotiate_interface cannot be null).
Should negotiation fail however, the layer is not skipped, but instead falls back to the traditional layer loading method which directly fetched the vkGet*ProcAddr functions from the layer.

PFN_vkNegotiateLoaderLayerInterfaceVersion negotiate_interface = NULL;
bool functions_in_interface = false;
if (!layer_prop->functions.str_negotiate_interface || strlen(layer_prop->functions.str_negotiate_interface) == 0) {
    negotiate_interface = (PFN_vkNegotiateLoaderLayerInterfaceVersion)loader_platform_get_proc_address(
        lib_handle, "vkNegotiateLoaderLayerInterfaceVersion");
} else {
    negotiate_interface = (PFN_vkNegotiateLoaderLayerInterfaceVersion)loader_platform_get_proc_address(
        lib_handle, layer_prop->functions.str_negotiate_interface);
}

// If we can negotiate an interface version, then we can also
// get everything we need from the one function call, so try
// that first, and see if we can get all the function pointers
// necessary from that one call.
if (NULL != negotiate_interface) {
    layer_prop->functions.negotiate_layer_interface = negotiate_interface;

    VkNegotiateLayerInterface interface_struct;

    if (loader_get_layer_interface_version(negotiate_interface, &interface_struct)) {
        // Go ahead and set the properties version to the
        // correct value.#include <vulkan/vk_layer.h>

__attribute__((visibility("default")))
VkResult vkNegotiateLoaderLayerInterfaceVersion(VkNegotiateLayerInterface*) {
    return VK_ERROR_INITIALIZATION_FAILED;
}

        layer_prop->interface_version = interface_struct.loaderLayerInterfaceVersion;

        // If the interface is 2 or newer, we have access to the
        // new GetPhysicalDeviceProcAddr function, so grab it,
        // and the other necessary functions, from the
        // structure.
        if (interface_struct.loaderLayerInterfaceVersion > 1) {
            cur_gipa = interface_struct.pfnGetInstanceProcAddr;
            cur_gdpa = interface_struct.pfnGetDeviceProcAddr;
            cur_gpdpa = interface_struct.pfnGetPhysicalDeviceProcAddr;
            if (cur_gipa != NULL) {
                // We've set the functions, so make sure we
                // don't do the unnecessary calls later.
                functions_in_interface = true;
            }
        }
    }
}

if (!functions_in_interface) {
    if ((cur_gipa = layer_prop->functions.get_instance_proc_addr) == NULL) {
        if (layer_prop->functions.str_gipa == NULL || strlen(layer_prop->functions.str_gipa) == 0) {
            cur_gipa =
                (PFN_vkGetInstanceProcAddr)loader_platform_get_proc_address(lib_handle, "vkGetInstanceProcAddr");
            layer_prop->functions.get_instance_proc_addr = cur_gipa;

            if (NULL == cur_gipa) {
                loader_log(inst, VULKAN_LOADER_ERROR_BIT | VULKAN_LOADER_LAYER_BIT, 0,
                           "loader_create_instance_chain: Failed to find \'vkGetInstanceProcAddr\' in layer \"%s\"",
                           layer_prop->lib_name);
                continue;
            }
        } else {
            cur_gipa = (PFN_vkGetInstanceProcAddr)loader_platform_get_proc_address(lib_handle,
                                                                                   layer_prop->functions.str_gipa);

            if (NULL == cur_gipa) {
                loader_log(inst, VULKAN_LOADER_ERROR_BIT | VULKAN_LOADER_LAYER_BIT, 0,
                           "loader_create_instance_chain: Failed to find \'%s\' in layer \"%s\"",
                           layer_prop->functions.str_gipa, layer_prop->lib_name);
                continue;
            }
        }
    }
}

This is problematic, because if a layer actually exports these functions (for whatever reason), the layer will still be loaded, even if explicitly disabled by failing negotiation.

Additionally, some layers can use the negotiation interface as a sort of "health check", checking for vital files or the application context it's negotiating in (or other things). For implicit layers, this will cause the following two confusing log messages to appear in the loader log (such as when running vulkaninfo):

ERROR: [Loader Message] Code 0 : loader_create_instance_chain: Failed to find 'vkGetInstanceProcAddr' in layer "liblsfg-vk-layer.so"
ERROR: [Loader Message] Code 0 : loader_create_device_chain: Failed to find 'vkGetInstanceProcAddr' in layer "liblsfg-vk-layer.so".  Skipping layer.

Ideally the negotiation behavior is corrected and the layer fails to load as the document suggests. At the very least, the two messages should be silenced as they will only cause confusion. Perhaps an additional INFO (not ERROR) message should be added if negotiation fails?

To Reproduce

#include <vulkan/vk_layer.h>

__attribute__((visibility("default")))
VkResult vkNegotiateLoaderLayerInterfaceVersion(VkNegotiateLayerInterface*) {
    return VK_ERROR_INITIALIZATION_FAILED;
}

Compiled with clang++ -shared ./src/entrypoint.cpp and set up as an implicit layer. Then typing vulkaninfo should show the log message (and confirm the behavior).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions