Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Instance

Any API that lets developers interact with a complex system must strike a balance between flexibility and ease of use. Vulkan goes unusually far on the flexibility side of this tradeoff by providing you with many tuning knobs at every stage of an execution process that most other GPU APIs largely hide from you. It therefore requires you to acquire an unusually good understanding of the complex process through which a GPU-based program gets things done.

In the first part of this course, we will make this complexity tractable by introducing it piece-wise, in the context of a trivial GPU program that merely squares an array of floating-point numbers. In the second part of the course, you will then see that once these basic concepts of Vulkan are understood, they easily scale them up to the complexity of a full Gray-Scott reaction.

As a first step, this chapter will cover how you can load the Vulkan library from Rust, set up a Vulkan instance in a way that eases later debugging, and enumerate available Vulkan devices.

Introducing vulkano

The first step that we must take before we can use Vulkan in Rust code, is to link your program to a Vulkan binding. This is a Rust crate that handles the hard work of linking to the Vulkan C library and exposing a Rust layer on top of it so that your Rust code may interact with it.

In this course, we will use the vulkano crate for this purpose. This crate builds on top of the auto-generated ash crate, which closely matches the Vulkan C API with only minor Rust-specific API tweaks, by supplementing it with two layers of abstraction:

  • A low-level layer that re-exposes Vulkan types and functions in a manner that is more in line with Rust programmer expectations. For example, C-style free functions that operate on their first pointer parameter are replaced with Rust-style structs with methods.
  • A high-level layer that automates away some common operations (like sub-allocation of GPU memory allocations into smaller chunks) and makes as many operations as possible safe (no possibility for undefined behavior).

Crucially, this layering is fine-grained (done individually for each Vulkan object type) and transparent (any high-level object lets you access the lower-level object below it). As a result, if you ever encounter a situation where the high-level layer has made design choices that are not right for your use case, you are always able to drop down to a lower-level layer and do things your own way.

This means that anything you can do with raw Vulkan API calls, you can also do with vulkano. But vulkano will usually give you an alternate way to do things that is easier, fast/flexible enough for most purposes, and requires a lot less unsafe Rust code that must be carefully audited for memory/thread/type safety. For many applications, this is a better tradeoff than using ash directly.

The vulkano dependency has already been added to this course’s example code, but for reference, this is how you would add it:

# You do not need to type in this command, it has already been done for you
cargo add --no-default-features --features=macros vulkano

This adds the vulkano dependency in a manner that disables the x11 feature that enables X11 support. This feature is not needed for this course, where we are not rendering images to X11 windows. And it won’t work in this course’s Linux containers, which do not contain a complete X11 stack as this would unnecessarily increase download size.

We do, however, keep the macros features on, because we will need it in order to use the vulkano-shaders crate later on. We’ll discuss what this crate does and why we need it in a future chapter.

Loading the library

Now that we have the vulkano binding available, we can use it to load the Vulkan library. In principle, you could customize this loading process to e.g. switch between different Vulkan libraries, but in practice this is rarely needed because as we will see later Vulkan provides several tools to customize the behavior of the library.

Hence, for the purpose of this course, we will stick with the default vulkano library-loading method, which is appropriate for almost every Vulkan application:

use std::error::Error;
use vulkano::library::VulkanLibrary;

// Simplify error handling with type-erased errors
type Result<T> = std::result::Result<T, Box<dyn Error>>;

fn main() -> Result<()> {
    // Load the Vulkan library
    let library = VulkanLibrary::new()?;

    // ...

    Ok(())
}

Like all system operations, loading the library can fail if e.g. no Vulkan implementation is installed on the host system, and we need to handle that.

Here, we choose to do it the easy way by converting the associated error type into a type-erased Box<dyn Error> type that can hold all error types, and bubbling this error out of the main() function using the ? error propagation operator. The Rust runtime will then take care of displaying the error message and aborting the program with a nonzero exit code. This basic error handling strategy is good enough for the simple utilities that we will be building throughout this course.

Once errors are handled, we may query the resulting VulkanLibrary object. For example, we can…

  • Check which revision of the Vulkan specification is supported. This versioning allows the Vulkan specification to evolve by telling us which newer features can be used by our application.
  • Check which Vulkan extensions are supported. Extensions allow Vulkan to support features that do not make sense on every single system supported by the API, such as the ability to display visuals in X11 and Wayland windows on Linux.
  • Check which Vulkan layers are available. Layers are stackable plugins that customize the behavior of your Vulkan library without replacing it. For example, the popular VK_LAYER_KHRONOS_validation layer adds error checking to all Vulkan functions, allowing you to check your application’s debug builds without slowing down its release builds.

Once we have learned what we need to know, we can then proceed with the next setup step, which is to set up a Vulkan API instance.

Setting up an instance

An Vulkan Instance is configured from a VulkanLibrary by specifying a few things about our application, including which optional Vulkan library features we want to use.

For reasons that will soon become clear, we must set up an Instance before we can do anything else with the Vulkan API, including enumerating available devices.

While the basic process is easy, we will take a few detours along the way to set up some optional Vulkan features that will make our debugging experience nicer later on.

vulkano configuration primer

For most configuration work, vulkano uses a recuring API design pattern that is based on configuration structs, where most fields have a default value.

When combined with Rust’s functional struct update syntax, this API design allows you to elegantly specify only the parameters that you care about. Here is an example:

use vulkano::instance::{InstanceCreateInfo, InstanceCreateFlags};

let instance_info = InstanceCreateInfo {
    flags: InstanceCreateFlags::ENUMERATE_PORTABILITY,
    ..InstanceCreateInfo::application_from_cargo_toml()
};

The above instance configuration struct expresses the following intent:

  • We let the Vulkan implementation expose devices that do not fully conform with the Vulkan specification, but only a slightly less featureful “portability subset” thereof. This is needed for some exotic Vulkan implementations like MoltenVk, which layers on top of macOS’ Metal API to work around Apple’s lack of Vulkan support.
  • We let vulkano infer the application name and version from our Cargo project’s metadata, so that we do not need to specify the same information in two different places.
  • For all other fields of the InstanceCreateInfo struct, we use the default instance configuration, which is to provide no extra information about our app to the Vulkan implementation and to enable no optional features.

Most optional Vulkan instance features are about interfacing with your operating system’s display features for rendering visuals on screen and are not useful for the kind of headless computations that we are going to study in this course. However, there are two optional Vulkan debugging features that we strongly advise enabling on every platform that supports them:

  • If the VK_LAYER_KHRONOS_validation layer is available, then it is a good idea to enable it in your debug builds. This enables debugging features falling in the following categories, at a runtime performance cost:
    • Error checking for Vulkan entry points, whose invalid usage normally results in instant Undefined Behavior. vulkano’s high level layer is already meant to prevent or report such incorrect usage, but unfortunately it is not immune to the occasional bug or limitation. It is thus good to have some defense-in-depth against UB in your debug builds before you try to report a GPU driver bug that later turns out to be a vulkano bug.
    • “Best practices” linting which detects suspicious API usage that is not illegal per the spec but may e.g. cause performance issues. This is basically a code linter executing at run-time with full knowledge of the application state.
    • Ability to use printf() in GPU code in order to easily investigate its state when it behaves unexpectedly, aka “Debug Printf”.
  • The VK_EXT_debug_utils extension lets you send diagnostic messages from the Vulkan implementation to your favorite log output (stderr, syslog…). I would advise enabling it for both debug and release builds, on all systems that support it.
    • In addition to being heavily used by the aforementioned validation layer, these messages often provide invaluable context when you are trying to diagnose why an application refuses to run as expected on someone else’s computer.

Indeed, these two debugging features are so important that vulkano provides dedicated tooling for enabling and configuring them. Let’s look into that.

Validation layer

As mentioned above, the Vulkan validation layer has some runtime overhead and partially duplicates the functionality of vulkano’s safe API. Therefore, it is normally only enabled in in debug builds.

We can check if the program is built in debug mode using the cfg!(debug_assertions) expression. When that is the case, we will want to check if the VK_LAYER_KHRONOS_validation layer is available, and if so add it to the set of layers that we enable for our instance:

// Set up a blank instance configuration.
//
// For what we are going to do here, an imperative style will be more effective
// than the functional style shown above, which is otherwise preferred.
let mut instance_info = InstanceCreateInfo::application_from_cargo_toml();

// In debug builds...
if cfg!(debug_assertions)
   // ...if the validation layer is available...
   && library.layer_properties()?
             .any(|layer| layer.name() == "VK_LAYER_KHRONOS_validation")
{
    // ...then enable it...
    instance_info
        .enabled_layers
        .push("VK_LAYER_KHRONOS_validation".into());

    // TODO: ...and configure it
}

// TODO: Proceed with rest of instance configuration

Back in the Vulkan 1.0 days, simply enabling the layer like this would have been enough. But as the TODO above suggests, the validation layer have since acquired optional features which are not enabled by default, largely because of their performance impact.

Because we only enable the validation layer in debug builds, where runtime performance is not a big concern, we can enable as many of those as we like by pushing the appropriate flags into the enabled_validation_features member of our InstanceCreateInfo struct. The only limitation that we must respect in doing so is that GPU-assisted validation (which provides extended error checking) is incompatible with use of printf() in GPU code. For the purpose of this course, we will priorize GPU-assisted validation over GPU printf().

The availability of these fine-grained settings is signaled by support of the VK_EXT_validation_features layer extension.1 We can detect this extension and enable it along with almost every feature except for GPU printf() using the following code:

use vulkano::instance::debug::ValidationFeatureEnable;

if library
    .supported_layer_extensions("VK_LAYER_KHRONOS_validation")?
    .ext_validation_features
{
    instance_info.enabled_extensions.ext_validation_features = true;
    instance_info.enabled_validation_features.extend([
        ValidationFeatureEnable::GpuAssisted,
        ValidationFeatureEnable::GpuAssistedReserveBindingSlot,
        ValidationFeatureEnable::BestPractices,
        ValidationFeatureEnable::SynchronizationValidation,
    ]);
}

And if we put it all together, we get the following validation layer setup routine:

/// Enable Vulkan validation layer in debug builds
fn enable_debug_validation(
    library: &VulkanLibrary,
    instance_info: &mut InstanceCreateInfo,
) -> Result<()> {
    // In debug builds...
    if cfg!(debug_assertions)
       // ...if the validation layer is available...
       && library.layer_properties()?
                 .any(|layer| layer.name() == "VK_LAYER_KHRONOS_validation")
    {
        // ...then enable it...
        instance_info
            .enabled_layers
            .push("VK_LAYER_KHRONOS_validation".into());

        // ...along with most available optional features
        if library
            .supported_layer_extensions("VK_LAYER_KHRONOS_validation")?
            .ext_validation_features
        {
            instance_info.enabled_extensions.ext_validation_features = true;
            instance_info.enabled_validation_features.extend([
                ValidationFeatureEnable::GpuAssisted,
                ValidationFeatureEnable::GpuAssistedReserveBindingSlot,
                ValidationFeatureEnable::BestPractices,
                ValidationFeatureEnable::SynchronizationValidation,
            ]);
        }
    }
    Ok(())
}

To conclude this section, it should be mentioned that the Vulkan validation layer is not featured in the default Vulkan setup of most Linux distributions, and must often be installed separately. For example, on Ubuntu, the vulkan-validationlayers separate package must be installed first. This is one reason why you should never force-enable validation layers in production Vulkan binaries.

Logging configuration

Now that validation layer has been taken care of, let us turn our attention to the other optional Vulkan debugging feature that we highlighted as worth enabling whenever possible, namely logging of messages from the Vulkan implementation.

Vulkan logging is configured using the DebugUtilsMessengerCreateInfo struct. There are three main things that we must specify here:

  • What message severities we want to handle.
    • As in most logging systems, a simple ERROR/WARNING/INFO/VERBOSE classification is used. But in Vulkan, enabling a certain severity does not implicitly enable higher severities, so you can e.g. handle ERROR and VERBOSE messages using different strategies without handling WARNING and INFO messages at all.
    • In typical Vulkan implementations, ERROR and WARNING messages should be an exceptional event, whereas INFO and VERBOSE messages can be sent at an unpleasantly high frequency. However an ERROR/WARNING message is often only understandable given the context of previous INFO/VERBOSE messages. It is therefore a good idea to print ERROR and WARNING messages by default, but provide an easy way to print INFO/VERBOSE messages too when needed.
  • What message types we want to handle.
    • Most Vulkan implementation messages will fall in the GENERAL category, but the validation layer may send messages in the VALIDATION and PERFORMANCE category too. As you may guess, the latter messages types report application correctness and runtime performance issues respectively.
  • What we want to do when a message matches the above criteria.
    • Building such a DebugUtilsMessengerCallback is unsafe because vulkano cannot check that your messaging callback, which is triggered by Vulkan API calls, does not make any Vulkan API calls itself. Doing so is forbidden for hopefully obvious reasons.2
    • Because we are building simple programs here, where the complexity of a production-grade logging system like syslog is unnecessary, we will simply forward these messages to stderr. For our first Vulkan program, an eprintln!() call will suffice.
    • Vulkan actually uses a form of structured logging, where the logging callback does not receive just a message string, but also a bunch of associated metadata about the context in which the message was emitted. In the interest of simplicity, our callback will only print out a subset of this metadata, which should be enough for our purposes.

As mentioned above, we should expose the message severity tradeoff to the user. We can do this using a simple clap CLI interface.

Here we will leverage clap’s Args feature, which lets us modularize our CLI arguments into several independent structs. This will later allow us to build multiple clap-based programs that share some common command-line arguments. Along the way, we will also expose the ability discussed in the beginning of this chapter to probe devices which are not fully Vulkan-compliant.

use clap::Args;

/// Vulkan instance configuration
#[derive(Debug, Args)]
pub struct InstanceOptions {
    /// Increase Vulkan log verbosity. Can be specified multiple times.
    #[arg(short, long, action = clap::ArgAction::Count)]
    pub verbose: u8,
}

Once we have that, we can set up some basic Vulkan logging configuration…

use vulkano::instance::debug::{
    DebugUtilsMessageSeverity, DebugUtilsMessageType,
    DebugUtilsMessengerCallback, DebugUtilsMessengerCreateInfo
};

/// Generate a Vulkan logging configuration
fn logger_info(options: &InstanceOptions) -> DebugUtilsMessengerCreateInfo {
    // Select accepted message severities
    type S = DebugUtilsMessageSeverity;
    let mut message_severity = S::ERROR | S::WARNING;
    if options.verbose >= 1 {
        message_severity |= S::INFO;
    }
    if options.verbose >= 2 {
        message_severity |= S::VERBOSE;
    }

    // Accept all message types
    type T = DebugUtilsMessageType;
    let message_type = T::GENERAL | T::VALIDATION | T::PERFORMANCE;

    // Define the callback that turns messages to logs on stderr
    // SAFETY: The logging callback makes no Vulkan API call
    let user_callback = unsafe {
        DebugUtilsMessengerCallback::new(|severity, ty, data| {
            // Format message identifiers, if any
            let id_name = data
                .message_id_name
                .map(|id_name| format!(" {id_name}"))
                .unwrap_or_default();
            let id_number = (data.message_id_number != 0)
                .then(|| format!(" #{}", data.message_id_number))
                .unwrap_or_default();

            // Put most information into a single stderr output
            eprintln!("[{severity:?} {ty:?}{id_name}{id_number}] {}", data.message);
        })
    };

    // Put it all together
    DebugUtilsMessengerCreateInfo {
        message_severity,
        message_type,
        ..DebugUtilsMessengerCreateInfo::user_callback(user_callback)
    }
}

Instance and logger creation

Now that we have a logger configuration, we are almost ready to enable logging. There are just two remaining concerns to take care of:

  • Logging uses the optional Vulkan VK_EXT_debug_utils extension that may not always be available. We must check for its presence and enable it if available.
  • For mysterious reasons, Vulkan allows programs to use different logging configurations at the time where an Instance is being set up and afterwards. This means that we will need to set up logging twice, once at the time where we create an Instance and another time after that.

After instance creation, logging is taken care of by a separate DebugUtilsMessenger object, which follows the usual RAII design: as long as it is alive, messages are logged, and once it is dropped, logging stop. If you want logging to happen for an application’s entire lifetime (which you usually do), the easiest way to avoid dropping this object too early is to bundle it with your other long-lived Vulkan objects in a long-lived “context” struct.

We will now demonstrate this pattern with a struct that combines a Vulkan instance with optional logging. Its constructor sets up all aforementioned features, including logging if available:

use std::sync::Arc;
use vulkano::instance::{
    debug::DebugUtilsMessenger, Instance, InstanceCreateFlags
};

/// Vulkan instance, with associated logging if available
pub struct LoggingInstance {
    pub instance: Arc<Instance>,
    pub messenger: Option<DebugUtilsMessenger>,
}
//
impl LoggingInstance {
    /// Set up a `LoggingInstance`
    pub fn new(library: Arc<VulkanLibrary>, options: &InstanceOptions) -> Result<Self> {
        // Prepare some basic instance configuration from Cargo metadata, and
        // enable portability subset device for macOS/MoltenVk compatibility
        let mut instance_info = InstanceCreateInfo {
            flags: InstanceCreateFlags::ENUMERATE_PORTABILITY,
            ..InstanceCreateInfo::application_from_cargo_toml()
        };

        // Enable validation layers in debug builds
        enable_debug_validation(&library, &mut instance_info)?;

        // Set up logging to stderr if the Vulkan implementation supports it
        let mut log_info = None;
        if library.supported_extensions().ext_debug_utils {
            instance_info.enabled_extensions.ext_debug_utils = true;
            let config = logger_info(options);
            instance_info.debug_utils_messengers.push(config.clone());
            log_info = Some(config);
        }

        // Set up instance, logging creation-time messages
        let instance = Instance::new(library, instance_info)?;

        // Keep logging after instance creation
        let instance2 = instance.clone();
        let messenger = log_info
            .map(move |config| DebugUtilsMessenger::new(instance2, config))
            .transpose()?;
        Ok(LoggingInstance {
            instance,
            messenger,
        })
    }
}

…and once we have that, we can query this instance to enumerate available devices on the system, for the purpose of picking (at least) one that we will eventually run computations on. This will be the topic of the next exercise, and the next chapter after that.

Exercise

Introducing info

The exercises/ codebase that you have been provided with contains a set of executable programs (in src/bin), that share some code via a common utility library (at the root of src/). Most of the code introduced in this chapter is located in the instance module of this utility library.

The info executable, whose source code lies in src/bin/info.rs, lets you query some properties of your system’s Vulkan setup. You can think of it as a simplified version of the classic vulkaninfo utility from the Linux vulkan-tools package, with a less overwhelming default configuration.

You can run this executable using the following Cargo command…

cargo run --bin info

…and if your Vulkan implementation is recent enough, you may notice that the validation layer is already doing its job by displaying some warnings:

Click here for example output
[WARNING VALIDATION VALIDATION-SETTINGS #2132353751] vkCreateInstance(): Both GPU Assisted Validation and Normal Core Check Validation are enabled, this is not recommend as it  will be very slow. Once all errors in Core Check are solved, please disable, then only use GPU-AV for best performance.
[WARNING VALIDATION BestPractices-specialuse-extension #1734198062] vkCreateInstance(): Attempting to enable extension VK_EXT_debug_utils, but this extension is intended to support use by applications when debugging and it is strongly recommended that it be otherwise avoided.
[WARNING VALIDATION BestPractices-deprecated-extension #-628989766] vkCreateInstance(): Attempting to enable deprecated extension VK_EXT_validation_features, but this extension has been deprecated by VK_EXT_layer_settings.
[WARNING VALIDATION BestPractices-specialuse-extension #1734198062] vkCreateInstance(): Attempting to enable extension VK_EXT_validation_features, but this extension is intended to support use by applications when debugging and it is strongly recommended that it be otherwise avoided.
Vulkan instance ready:
- Max API version: 1.3.281
- Physical devices:
[WARNING VALIDATION WARNING-GPU-Assisted-Validation #615892639] vkGetPhysicalDeviceProperties2(): Internal Warning: Setting VkPhysicalDeviceVulkan12Properties::maxUpdateAfterBindDescriptorsInAllPools to 32
[WARNING VALIDATION WARNING-GPU-Assisted-Validation #615892639] vkGetPhysicalDeviceProperties2(): Internal Warning: Setting VkPhysicalDeviceVulkan12Properties::maxUpdateAfterBindDescriptorsInAllPools to 32
  0. AMD Radeon Pro WX 3200 Series (RADV POLARIS12)
     * Device type: DiscreteGpu
  1. llvmpipe (LLVM 20.1.6, 256 bits)
     * Device type: Cpu

Thankfully, these warnings are mostly inconsequential:

  • The VALIDATION-SETTINGS warning complains that we are using an unnecessarily exhaustive validation configuration, which can have a strong averse effect on runtime performance. It suggests running the program multiple times with less extensive validation. This is cumbersome, though, which is why in this course we just let debug builds be slow.
  • The BestPractices-specialuse-extension warnings complain about our use of debugging-focused extensions. But we do it on purpose to make debugging easier.
  • The BestPractices-deprecated-extension warning complains about a genuine issue (we are using an old extension to configure the validation layer), however we can’t easily fix this issue right now (vulkano does not support the new configuration mechanism yet).
  • The WARNING-GPU-Assisted-Validation warnings complain about an internal implementation detail of GPU-assisted validation that we have no control on. It suggests a possible bug in GPU-assisted validation that should be reported at some point.

Other operating modes

By running a release build of the program instead, we see that the warnings go away, highlighting the fact that validation layers are only enabled in debug builds:

cargo run --release --bin info
Click here for example output
Vulkan instance ready:
- Max API version: 1.3.281
- Physical devices:
  0. AMD Radeon Pro WX 3200 Series (RADV POLARIS12)
     * Device type: DiscreteGpu
  1. llvmpipe (LLVM 20.1.6, 256 bits)
     * Device type: Cpu

…however, if you increase the Vulkan log verbosity by specifying the -v command-line option to the output binary (which goes after a -- to separate it from Cargo options), you will see that Vulkan logging remains enabled even in release builds, as we would expect.

cargo run --release --bin info -- -v
Click here for example output
[INFO GENERAL Loader Message] No valid vk_loader_settings.json file found, no loader settings will be active
[INFO GENERAL Loader Message] Searching for implicit layer manifest files
[INFO GENERAL Loader Message]    In following locations:
[INFO GENERAL Loader Message]       /home/hadrien/.config/vulkan/implicit_layer.d
[INFO GENERAL Loader Message]       /home/hadrien/.config/kdedefaults/vulkan/implicit_layer.d
[INFO GENERAL Loader Message]       /etc/xdg/vulkan/implicit_layer.d
[INFO GENERAL Loader Message]       /etc/vulkan/implicit_layer.d
[INFO GENERAL Loader Message]       /home/hadrien/.local/share/vulkan/implicit_layer.d
[INFO GENERAL Loader Message]       /home/hadrien/.local/share/flatpak/exports/share/vulkan/implicit_layer.d
[INFO GENERAL Loader Message]       /var/lib/flatpak/exports/share/vulkan/implicit_layer.d
[INFO GENERAL Loader Message]       /usr/local/share/vulkan/implicit_layer.d
[INFO GENERAL Loader Message]       /usr/share/vulkan/implicit_layer.d
[INFO GENERAL Loader Message]    Found the following files:
[INFO GENERAL Loader Message]       /etc/vulkan/implicit_layer.d/renderdoc_capture.json
[INFO GENERAL Loader Message]       /usr/share/vulkan/implicit_layer.d/MangoHud.x86_64.json
[INFO GENERAL Loader Message]       /usr/share/vulkan/implicit_layer.d/VkLayer_MESA_device_select.json
[INFO GENERAL Loader Message] Found manifest file /etc/vulkan/implicit_layer.d/renderdoc_capture.json (file version 1.1.2)
[INFO GENERAL Loader Message] Found manifest file /usr/share/vulkan/implicit_layer.d/MangoHud.x86_64.json (file version 1.0.0)
[INFO GENERAL Loader Message] Found manifest file /usr/share/vulkan/implicit_layer.d/VkLayer_MESA_device_select.json (file version 1.0.0)
[INFO GENERAL Loader Message] Searching for explicit layer manifest files
[INFO GENERAL Loader Message]    In following locations:
[INFO GENERAL Loader Message]       /home/hadrien/.config/vulkan/explicit_layer.d
[INFO GENERAL Loader Message]       /home/hadrien/.config/kdedefaults/vulkan/explicit_layer.d
[INFO GENERAL Loader Message]       /etc/xdg/vulkan/explicit_layer.d
[INFO GENERAL Loader Message]       /etc/vulkan/explicit_layer.d
[INFO GENERAL Loader Message]       /home/hadrien/.local/share/vulkan/explicit_layer.d
[INFO GENERAL Loader Message]       /home/hadrien/.local/share/flatpak/exports/share/vulkan/explicit_layer.d
[INFO GENERAL Loader Message]       /var/lib/flatpak/exports/share/vulkan/explicit_layer.d
[INFO GENERAL Loader Message]       /usr/local/share/vulkan/explicit_layer.d
[INFO GENERAL Loader Message]       /usr/share/vulkan/explicit_layer.d
[INFO GENERAL Loader Message]    Found the following files:
[INFO GENERAL Loader Message]       /usr/share/vulkan/explicit_layer.d/VkLayer_api_dump.json
[INFO GENERAL Loader Message]       /usr/share/vulkan/explicit_layer.d/VkLayer_monitor.json
[INFO GENERAL Loader Message]       /usr/share/vulkan/explicit_layer.d/VkLayer_screenshot.json
[INFO GENERAL Loader Message]       /usr/share/vulkan/explicit_layer.d/VkLayer_khronos_validation.json
[INFO GENERAL Loader Message]       /usr/share/vulkan/explicit_layer.d/VkLayer_INTEL_nullhw.json
[INFO GENERAL Loader Message]       /usr/share/vulkan/explicit_layer.d/VkLayer_MESA_overlay.json
[INFO GENERAL Loader Message]       /usr/share/vulkan/explicit_layer.d/VkLayer_MESA_screenshot.json
[INFO GENERAL Loader Message]       /usr/share/vulkan/explicit_layer.d/VkLayer_MESA_vram_report_limit.json
[INFO GENERAL Loader Message] Found manifest file /usr/share/vulkan/explicit_layer.d/VkLayer_api_dump.json (file version 1.2.0)
[INFO GENERAL Loader Message] Found manifest file /usr/share/vulkan/explicit_layer.d/VkLayer_monitor.json (file version 1.0.0)
[INFO GENERAL Loader Message] Found manifest file /usr/share/vulkan/explicit_layer.d/VkLayer_screenshot.json (file version 1.2.0)
[INFO GENERAL Loader Message] Found manifest file /usr/share/vulkan/explicit_layer.d/VkLayer_khronos_validation.json (file version 1.2.0)
[INFO GENERAL Loader Message] Found manifest file /usr/share/vulkan/explicit_layer.d/VkLayer_INTEL_nullhw.json (file version 1.0.0)
[INFO GENERAL Loader Message] Found manifest file /usr/share/vulkan/explicit_layer.d/VkLayer_MESA_overlay.json (file version 1.0.0)
[INFO GENERAL Loader Message] Found manifest file /usr/share/vulkan/explicit_layer.d/VkLayer_MESA_screenshot.json (file version 1.0.0)
[INFO GENERAL Loader Message] Found manifest file /usr/share/vulkan/explicit_layer.d/VkLayer_MESA_vram_report_limit.json (file version 1.0.0)
[INFO GENERAL Loader Message] Searching for driver manifest files
[INFO GENERAL Loader Message]    In following locations:
[INFO GENERAL Loader Message]       /home/hadrien/.config/vulkan/icd.d
[INFO GENERAL Loader Message]       /home/hadrien/.config/kdedefaults/vulkan/icd.d
[INFO GENERAL Loader Message]       /etc/xdg/vulkan/icd.d
[INFO GENERAL Loader Message]       /etc/vulkan/icd.d
[INFO GENERAL Loader Message]       /home/hadrien/.local/share/vulkan/icd.d
[INFO GENERAL Loader Message]       /home/hadrien/.local/share/flatpak/exports/share/vulkan/icd.d
[INFO GENERAL Loader Message]       /var/lib/flatpak/exports/share/vulkan/icd.d
[INFO GENERAL Loader Message]       /usr/local/share/vulkan/icd.d
[INFO GENERAL Loader Message]       /usr/share/vulkan/icd.d
[INFO GENERAL Loader Message]    Found the following files:
[INFO GENERAL Loader Message]       /usr/share/vulkan/icd.d/radeon_icd.x86_64.json
[INFO GENERAL Loader Message]       /usr/share/vulkan/icd.d/lvp_icd.x86_64.json
[INFO GENERAL Loader Message] Found ICD manifest file /usr/share/vulkan/icd.d/radeon_icd.x86_64.json, version 1.0.0
[INFO GENERAL Loader Message] Found ICD manifest file /usr/share/vulkan/icd.d/lvp_icd.x86_64.json, version 1.0.0
[INFO GENERAL Loader Message] Insert instance layer "VK_LAYER_MESA_device_select" (libVkLayer_MESA_device_select.so)
[INFO GENERAL Loader Message] vkCreateInstance layer callstack setup to:
[INFO GENERAL Loader Message]    <Application>
[INFO GENERAL Loader Message]      ||
[INFO GENERAL Loader Message]    <Loader>
[INFO GENERAL Loader Message]      ||
[INFO GENERAL Loader Message]    VK_LAYER_MESA_device_select
[INFO GENERAL Loader Message]            Type: Implicit
[INFO GENERAL Loader Message]            Enabled By: Implicit Layer
[INFO GENERAL Loader Message]                Disable Env Var:  NODEVICE_SELECT
[INFO GENERAL Loader Message]            Manifest: /usr/share/vulkan/implicit_layer.d/VkLayer_MESA_device_select.json
[INFO GENERAL Loader Message]            Library:  libVkLayer_MESA_device_select.so
[INFO GENERAL Loader Message]      ||
[INFO GENERAL Loader Message]    <Drivers>
Vulkan instance ready:
- Max API version: 1.3.281
- Physical devices:
[INFO GENERAL Loader Message] linux_read_sorted_physical_devices:
[INFO GENERAL Loader Message]      Original order:
[INFO GENERAL Loader Message]            [0] llvmpipe (LLVM 20.1.6, 256 bits)
[INFO GENERAL Loader Message]            [1] AMD Radeon Pro WX 3200 Series (RADV POLARIS12)
[INFO GENERAL Loader Message]      Sorted order:
[INFO GENERAL Loader Message]            [0] AMD Radeon Pro WX 3200 Series (RADV POLARIS12)  
[INFO GENERAL Loader Message]            [1] llvmpipe (LLVM 20.1.6, 256 bits)  
[INFO GENERAL Loader Message] linux_read_sorted_physical_devices:
[INFO GENERAL Loader Message]      Original order:
[INFO GENERAL Loader Message]            [0] llvmpipe (LLVM 20.1.6, 256 bits)
[INFO GENERAL Loader Message]            [1] AMD Radeon Pro WX 3200 Series (RADV POLARIS12)
[INFO GENERAL Loader Message]      Sorted order:
[INFO GENERAL Loader Message]            [0] AMD Radeon Pro WX 3200 Series (RADV POLARIS12)  
[INFO GENERAL Loader Message]            [1] llvmpipe (LLVM 20.1.6, 256 bits)  
[INFO GENERAL Loader Message] linux_read_sorted_physical_devices:
[INFO GENERAL Loader Message]      Original order:
[INFO GENERAL Loader Message]            [0] llvmpipe (LLVM 20.1.6, 256 bits)
[INFO GENERAL Loader Message]            [1] AMD Radeon Pro WX 3200 Series (RADV POLARIS12)
[INFO GENERAL Loader Message]      Sorted order:
[INFO GENERAL Loader Message]            [0] AMD Radeon Pro WX 3200 Series (RADV POLARIS12)  
[INFO GENERAL Loader Message]            [1] llvmpipe (LLVM 20.1.6, 256 bits)  
[INFO GENERAL Loader Message] linux_read_sorted_physical_devices:
[INFO GENERAL Loader Message]      Original order:
[INFO GENERAL Loader Message]            [0] llvmpipe (LLVM 20.1.6, 256 bits)
[INFO GENERAL Loader Message]            [1] AMD Radeon Pro WX 3200 Series (RADV POLARIS12)
[INFO GENERAL Loader Message]      Sorted order:
[INFO GENERAL Loader Message]            [0] AMD Radeon Pro WX 3200 Series (RADV POLARIS12)  
[INFO GENERAL Loader Message]            [1] llvmpipe (LLVM 20.1.6, 256 bits)  
  0. AMD Radeon Pro WX 3200 Series (RADV POLARIS12)
     * Device type: DiscreteGpu
  1. llvmpipe (LLVM 20.1.6, 256 bits)
     * Device type: Cpu

Hands-on

You can query the full list of available command-line flags using the standard --help command option, which goes after -- like other non-Cargo options. Please play around with the various available CLI options and try to use this utility to answer the following questions:

  • Is your computer’s GPU correctly detected, or do you only see a llvmpipe CPU emulation device (or worse, no device at all) ?
    • Please report absence of a GPU device to the teacher, with a bit of luck we may find the right system configuration tweak to get it to work.
  • What optional instance extensions and layers does your Vulkan implementation support?
  • How much device-local memory do your GPUs have ?
  • What Vulkan extensions do your GPUs support ?
  • (Linux-specific) Can you tell where on disk the shared libraries featuring Vulkan drivers (known as Installable Client Drivers or ICDs in Khronos API jargon) are stored ?

Once your thirst for system configuration knowledge is quenched, you may then study the source code of this program. Which is admittedly not the prettiest as it priorizes beginner readability over maximal maintainability in more than one place…

Overall, this program demonstrates how various system properties can be queried using the VulkanLibrary and Instance APIs. But not all available properties are exposed because the Vulkan specification is huge and we are only going to cover a subset of it in this course. However, if any property in the documentation linked above gets you curious, do not hesitate to adjust the code of the info program so that it gets printed as well!


  1. …which has recently been deprecated and scheduled for replacement by VK_EXT_layer_settings, but alas vulkano does not support this new layer configuration mechanism yet.

  2. The Vulkan messaging API allows for synchronous implementations. In such implementations, when a Vulkan API call emits a message, it is interrupted midway through its internal processing while the message is being processed. This means that the Vulkan API implementation may be in an inconsistent state (e.g. some thread-local mutex may be locked). If our message processing callback then proceeds to make another Vulkan API call, this new API call will observe that inconsistent implementation state, which can result in an arbitrarily bad outcome (e.g. a thread deadlock in the above example). Furthermore, the new Vulkan API call could later emit more messages, potentially resulting in infinite recursion.