DEV Community

Kaustuv Pokharel
Kaustuv Pokharel

Posted on

Introduction: VULKAN API

1. Vulkan applications start by creating a Vulkan Instance that defines the Vulkan application.

A Vulkan instance is the foundation of a Vulkan application. It represents the connection between your application and the Vulkan library. The instance provides access to the Vulkan API and allows you to configure global settings like enabling extensions or validation layers.

Steps to Create a Vulkan Instance:

  1. Specify application and engine information (optional but good practice).
  2. List desired extensions and validation layers.
  3. Call vkCreateInstance.

Code Example:

#include <vulkan/vulkan.h>
#include <iostream>
#include <vector>

// Main function
int main() {
    VkInstance instance;

    // Application Info (optional)
    VkApplicationInfo appInfo{};
    appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
    appInfo.pApplicationName = "Vulkan App";
    appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
    appInfo.pEngineName = "No Engine";
    appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
    appInfo.apiVersion = VK_API_VERSION_1_3;

    // Instance Create Info
    VkInstanceCreateInfo createInfo{};
    createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    createInfo.pApplicationInfo = &appInfo;

    // Create Vulkan Instance
    if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
        std::cerr << "Failed to create Vulkan instance!" << std::endl;
        return -1;
    }

    std::cout << "Vulkan instance created successfully!" << std::endl;

    // Cleanup
    vkDestroyInstance(instance, nullptr);
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

2. Enumerate Physical Devices to pick an adequate GPU.

Physical devices represent actual GPUs connected to your machine. Vulkan allows you to enumerate them and pick the most suitable one.

Steps:

  1. Enumerate all physical devices using vkEnumeratePhysicalDevices.
  2. Evaluate their properties and features.
  3. Choose a device that meets your needs.

Key Properties to Check:

  • Device type (e.g., discrete GPU, integrated GPU).
  • Queue families (e.g., graphics, compute).
  • Supported extensions.

Code Example:

void pickPhysicalDevice(VkInstance instance, VkPhysicalDevice &physicalDevice) {
    uint32_t deviceCount = 0;
    vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);

    if (deviceCount == 0) {
        throw std::runtime_error("Failed to find GPUs with Vulkan support!");
    }

    std::vector<VkPhysicalDevice> devices(deviceCount);
    vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());

    for (const auto &device : devices) {
        VkPhysicalDeviceProperties deviceProperties;
        vkGetPhysicalDeviceProperties(device, &deviceProperties);

        std::cout << "Found GPU: " << deviceProperties.deviceName << std::endl;
        // Check for desired features here (e.g., graphics queue support).
        physicalDevice = device;
        return;
    }

    throw std::runtime_error("Failed to find a suitable GPU!");
}
Enter fullscreen mode Exit fullscreen mode

3. Create Logical Device to interface with the chosen Physical Device.

A logical device is an abstraction over the physical device that allows you to interface with it. When creating a logical device, you also request specific features and queues.

Steps:

  1. Define required queue families.
  2. Specify desired extensions.
  3. Call vkCreateDevice.

Code Example:

void createLogicalDevice(VkPhysicalDevice physicalDevice, VkDevice &logicalDevice) {
    // Define the queue we need (e.g., graphics queue)
    float queuePriority = 1.0f;
    VkDeviceQueueCreateInfo queueCreateInfo{};
    queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
    queueCreateInfo.queueFamilyIndex = 0; // Assuming index 0 is valid for graphics
    queueCreateInfo.queueCount = 1;
    queueCreateInfo.pQueuePriorities = &queuePriority;

    // Specify device features (if any)
    VkPhysicalDeviceFeatures deviceFeatures{};

    // Create the logical device
    VkDeviceCreateInfo createInfo{};
    createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
    createInfo.queueCreateInfoCount = 1;
    createInfo.pQueueCreateInfos = &queueCreateInfo;
    createInfo.pEnabledFeatures = &deviceFeatures;

    if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &logicalDevice) != VK_SUCCESS) {
        throw std::runtime_error("Failed to create logical device!");
    }

    std::cout << "Logical device created successfully!" << std::endl;
}
Enter fullscreen mode Exit fullscreen mode

4. Multiple types of queue (queue families) on a Physical Device can be assigned to a Logical Device. Each one processes different types of command.

Queue families on a physical device process different types of commands (e.g., rendering, compute, transfer). Each queue family has capabilities such as:

  • Graphics commands.
  • Compute operations.
  • Data transfers.

Finding Queue Families:

  1. Query queue family properties with vkGetPhysicalDeviceQueueFamilyProperties.
  2. Check if a family supports required operations (e.g., graphics).

Code Example:

int findQueueFamilies(VkPhysicalDevice device) {
    uint32_t queueFamilyCount = 0;
    vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);

    std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
    vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());

    for (int i = 0; i < queueFamilies.size(); i++) {
        if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
            return i; // Found a graphics queue
        }
    }

    throw std::runtime_error("Failed to find a suitable queue family!");
}
Enter fullscreen mode Exit fullscreen mode

5. Extensions can be applied, such as extensions to enable displaying to a window.

Extensions enhance Vulkan's functionality, such as enabling window surface creation. Extensions are specified during instance or logical device creation.

Example with GLFW:

uint32_t glfwExtensionCount = 0;
const char **glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

VkInstanceCreateInfo createInfo{};
createInfo.enabledExtensionCount = glfwExtensionCount;
createInfo.ppEnabledExtensionNames = glfwExtensions;
Enter fullscreen mode Exit fullscreen mode

6. Validation Layers are optional and allow us to validate our code at run-time.

Validation layers help debug Vulkan applications. They catch errors during development but can be disabled in release builds.

Example to Enable Validation Layers:

const char *validationLayers[] = {"VK_LAYER_KHRONOS_validation"};

VkInstanceCreateInfo createInfo{};
createInfo.enabledLayerCount = 1;
createInfo.ppEnabledLayerNames = validationLayers;
Enter fullscreen mode Exit fullscreen mode

Top comments (0)