c++vulkan

Validation Error: VK_KHR_portability_subset must be enabled because physical device VkPhysicalDevice


hello every one have error

This error message indicates that the VK_KHR_portability_subset extension must be enabled when creating the Vulkan device. It seems that the Vulkan implementation on macOS (using MoltenVK) requires this extension for proper functionality.

In my Vulkan application, I'm trying to create a logical device without enabling this extension, which is likely causing the error. I suspect that I need to include VK_KHR_portability_subset in the list of required device extensions when setting up the VkDeviceCreateInfo structure.

Request for Help:

Could someone please help me understand why this error is occurring and how to properly enable the VK_KHR_portability_subset extension in my Vulkan application on macOS? Any guidance on resolving this validation error would be greatly appreciated.

validation layer: Validation Error: \[ VUID-VkDeviceCreateInfo-pProperties-04451 \] Object 0: handle = 0x600002e50360, type = VK_OBJECT_TYPE_PHYSICAL_DEVICE; | MessageID = 0x3a3b6ca0 | vkCreateDevice():  VK_KHR_portability_subset must be enabled because physical device VkPhysicalDevice 0x600002e50360\[

can i got help thanks


#ifndef WINDOW_H
#define WINDOW_H

#define GLFW_INCLUDE_VULKAN

#include <GLFW/glfw3.h>
#include <iostream>
#include<vector>
#include<optional>

#ifdef NDEBUG
const bool enableValidationLayers = false;
#else
const bool enableValidationLayers = true;
#endif


namespace Application {

    struct QueueFamilyIndices {
        std::optional<uint32_t> graphicsFamily;

        std::optional<uint32_t> presentFamily;

        bool isComplete() {
            return graphicsFamily.has_value();
        }
    };


    struct SwapChainSupportDetails {
        VkSurfaceCapabilitiesKHR capabilities;
        std::vector<VkSurfaceFormatKHR> formats;
        std::vector<VkPresentModeKHR> presentModes;
    };


    class Window {
    public:
        void run();

    private:
        GLFWwindow *window;
        uint32_t WIDTH = 800;
        uint32_t HEIGHT = 600;
        VkInstance instance;
        VkDebugUtilsMessengerEXT debugMessenger;
        const std::vector<const char *> validationLayers = {
            "VK_LAYER_KHRONOS_validation"
        };


        void initWindow();

        void initVulkan();

        void mainLoop() const;

        void cleanup() const;

        void setupDebugMessenger();


        std::vector<const char *> getRequiredExtensions(bool includePortabilityExtension = true);

        void extensionProperties() const;


        bool checkValidationLayerSupport();


        static void mouse_button_callback(GLFWwindow *window, int button, int action, int mods);


        void createInstance();


        VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;



        QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device);

        void pickPhysicalDevice();



        bool isDeviceSuitable(const VkPhysicalDevice &device, int &score);


        int rateDeviceSuitability(VkPhysicalDevice devices);


        VkDevice device;

        VkQueue graphicsQueue;


        void createLogicalDevice();


        VkSurfaceKHR surface;

        void createSurface();


        VkQueue presentQueue;


        const std::vector<const char *> deviceExtensions = {
            VK_KHR_SWAPCHAIN_EXTENSION_NAME
        };


        bool checkDeviceExtensionSupport(VkPhysicalDevice device);




        SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device);
    };
}

#endif // WINDOW_HPP
//
// Created by Abdulkafi on 03/10/2024 A.
//
#include "Window.hpp"
#include <iostream>
#include <stdexcept>
#include <set>



namespace Application {
    static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
        VkDebugUtilsMessageSeverityFlagBitsEXT meesageSeverity,
        VkDebugUtilsMessageTypeFlagsEXT messageType,
        const VkDebugUtilsMessengerCallbackDataEXT *pCallbackDate,
        void *pUserData) {
        std::cerr << "validation layer: " << pCallbackDate->pMessage << std::endl;
        return VK_FALSE;
    }

    VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT *pCreateInfo,
                                          const VkAllocationCallbacks *pAllocator,
                                          VkDebugUtilsMessengerEXT *pDebugMessenger) {
        auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance,
                                                                               "vkCreateDebugUtilsMessengerEXT");
        if (func != nullptr) {
            return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
        } else {
            return VK_ERROR_EXTENSION_NOT_PRESENT;
        }
    }

    void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger,
                                       const VkAllocationCallbacks *pAllocator) {
        auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance,
            "vkDestroyDebugUtilsMessengerEXT");
        if (func != nullptr) {
            func(instance, debugMessenger, pAllocator);
        }
    }

    void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT &createInfo) {
        createInfo = {};
        createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
        createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
                                     VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
                                     VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
        createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
                                 VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
                                 VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
        createInfo.pfnUserCallback = debugCallback;
    }

    void Window::setupDebugMessenger() {
        if (!enableValidationLayers) return;

        VkDebugUtilsMessengerCreateInfoEXT createInfoExt{};
        populateDebugMessengerCreateInfo(createInfoExt);

        if (CreateDebugUtilsMessengerEXT(instance, &createInfoExt, nullptr, &debugMessenger) != VK_SUCCESS) {
            throw std::runtime_error("Failed to set up debug messenger!");
        }
    }


    void Window::run() {
        initWindow();
        initVulkan();
        mainLoop();
        cleanup();
    }

    void Window::initWindow() {
        if (!glfwInit()) {
            throw std::runtime_error("Failed to initialize GLFW");
        }

        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
        glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);


        GLFWmonitor *primaryMonitor = glfwGetPrimaryMonitor();
        if (!primaryMonitor) {
            glfwTerminate();
            throw std::runtime_error("Failed to get primary monitor");
        }


        const GLFWvidmode *videoMode = glfwGetVideoMode(primaryMonitor);
        if (videoMode) {
            std::cout << "Screen Width: " << videoMode->width << std::endl;
            std::cout << "Screen Height: " << videoMode->height << std::endl;
            std::cout << "Refresh Rate: " << videoMode->refreshRate << " Hz" << std::endl;
        }


        WIDTH = videoMode->width;
        HEIGHT = videoMode->height;


        window = glfwCreateWindow(WIDTH / 2, HEIGHT / 2, "Abdulkafi", nullptr, nullptr);
        if (!window) {
            glfwTerminate();
            throw std::runtime_error("Failed to create GLFW window");
        }



        glfwMakeContextCurrent(window);


        glfwSetMouseButtonCallback(window, mouse_button_callback);
    }

    void Window::initVulkan() {
        createInstance();
        setupDebugMessenger();
        createSurface();
        pickPhysicalDevice();
        createLogicalDevice();
    }


    void Window::mouse_button_callback(GLFWwindow *window, int button, int action, int mods) {
        if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) {
            std::cout << "mouse left!" << std::endl;
        } else if (button == GLFW_MOUSE_BUTTON_RIGHT && action == GLFW_PRESS) {
            std::cout << "mouse right!" << std::endl;
        }
    }

    void Window::mainLoop() const {
        while (!glfwWindowShouldClose(window)) {
            glfwPollEvents();
        }
    }

    void Window::cleanup() const {
        vkDestroyDevice(device, nullptr);

        if (enableValidationLayers) {
            DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
        }
        vkDestroySurfaceKHR(instance, surface, nullptr);
        vkDestroyInstance(instance, nullptr);
        glfwDestroyWindow(window);
        glfwTerminate();
    }


    void Window::createInstance() {
        if (enableValidationLayers && !checkValidationLayerSupport()) {
            throw std::runtime_error("validation layers requested, but not available!");
        }

        VkApplicationInfo appInfo{};
        appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
        appInfo.pApplicationName = "Abdulkafi BK Game";
        appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
        appInfo.pEngineName = "no Engine";
        appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
        appInfo.apiVersion = VK_MAKE_VERSION(1, 0, 0);


        VkInstanceCreateInfo instanceCreateInfo{};
        instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
        instanceCreateInfo.flags = 0;
        instanceCreateInfo.pApplicationInfo = &appInfo;

        instanceCreateInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
        std::vector<const char *> requiredextensions = getRequiredExtensions();


        instanceCreateInfo.enabledExtensionCount = static_cast<uint32_t>(requiredextensions.size());
        instanceCreateInfo.ppEnabledExtensionNames = requiredextensions.data();

        VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{};
        if (enableValidationLayers) {
            instanceCreateInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
            instanceCreateInfo.ppEnabledLayerNames = validationLayers.data();
            populateDebugMessengerCreateInfo(debugCreateInfo);
            instanceCreateInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT *) &debugCreateInfo;
        } else {
            instanceCreateInfo.enabledLayerCount = 0;
            instanceCreateInfo.pNext = nullptr;
        }


        if (vkCreateInstance(&instanceCreateInfo, nullptr, &instance) != VK_SUCCESS) {
            throw std::runtime_error("Failed to create instance");
        }
#ifdef DEBUG_MODE
        std::cout << "VkInstance created successfully. Handle value: " << reinterpret_cast<uintptr_t>(instance)
                << std::endl;
        extensionProperties();
#endif
    }


    void Window::extensionProperties() const {

        uint32_t extensionCount = 0;
        VkResult result = vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
        if (result != VK_SUCCESS) {
            throw std::runtime_error("Failed to enumerate instance extension properties.");
        }


        std::vector<VkExtensionProperties> extensions(extensionCount);
        result = vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
        if (result != VK_SUCCESS) {
            throw std::runtime_error("Failed to enumerate instance extension properties.");
        }


        std::cout << "Available extensions: " << extensionCount << std::endl;
        for (const auto &extension: extensions) {
            std::cout << "  Name: " << extension.extensionName
                    << " | Version: " << extension.specVersion << std::endl;
        }
    }

    std::vector<const char *> Window::getRequiredExtensions(bool includePortabilityExtension) {

        uint32_t glfwExtensionCount = 0;
        const char **glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
        if (!glfwExtensions) {
            throw std::runtime_error("Failed to get required GLFW extensions");
        }


        std::set<const char *> requiredExtensionsSet(glfwExtensions, glfwExtensions + glfwExtensionCount);


        if (includePortabilityExtension) {
            requiredExtensionsSet.insert(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
        }


        std::vector<const char *> requiredExtensions(requiredExtensionsSet.begin(), requiredExtensionsSet.end());
        if (enableValidationLayers) {
            requiredExtensions.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
        }
#ifdef DEBUG_MODE

        std::cout << "Required extensions count: " << requiredExtensions.size() << std::endl;
        for (const auto &extension: requiredExtensions) {
            std::cout << extension << std::endl;
        }
#endif

        return requiredExtensions;
    }

    bool Window::checkValidationLayerSupport() {
        uint32_t layerCount;
        vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
        std::vector<VkLayerProperties> availableLayers(layerCount);
        vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());
        for (const char *layerName: validationLayers) {
            bool layerFound = false;
            for (const auto &layerProperties: availableLayers) {
                if (strcmp(layerName, layerProperties.layerName) == 0) {
                    layerFound = true;
                    break;
                }
            }
            if (!layerFound) {
                return false;
            }
        }
        return true;
    }



    bool Window::isDeviceSuitable(const VkPhysicalDevice &device, int &score) {
        VkPhysicalDeviceProperties deviceProperties;
        VkPhysicalDeviceFeatures deviceFeatures;


        vkGetPhysicalDeviceProperties(device, &deviceProperties);
        vkGetPhysicalDeviceFeatures(device, &deviceFeatures);


        score = 0;


        if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
            score += 1000; 
        } else if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) {
            score += 500; 
        }


        score += deviceProperties.limits.maxImageDimension2D / 1000;


        if (deviceFeatures.geometryShader) {
            score += 100; 
        }


        return score > 0;
    }


    QueueFamilyIndices Window::findQueueFamilies(VkPhysicalDevice device) {
        QueueFamilyIndices indices;

        uint32_t queueFamilyCount = 0;
        vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);

        if (queueFamilyCount == 0) {
            return indices;
        }

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

        int i = 0;
        for (const auto &queueFamily: queueFamilies) {
            if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
                indices.graphicsFamily = i;
            }


            VkBool32 presentSupport = false;
            vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
            if (presentSupport) {
                indices.presentFamily = i;
            }

            if (indices.isComplete()) {
                break;
            }
            i++;
        }

        return indices;
    }

    void Window::pickPhysicalDevice() {
        uint32_t deviceCount = 0;

        vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
        if (deviceCount == 0) {
            throw std::runtime_error("coldn't get physical device");
        }

        std::vector<VkPhysicalDevice> devices(deviceCount);
        vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
        std::cout << "found device " << deviceCount << " physical" << std::endl;

        int highestScore = -1;
        VkPhysicalDevice bestDevice = VK_NULL_HANDLE;


        for (const auto &device: devices) {
            int currentScore = 0;


            if (isDeviceSuitable(device, currentScore)) {
                QueueFamilyIndices indices = findQueueFamilies(device);


                if (indices.isComplete() && currentScore > highestScore) {
                    highestScore = currentScore;
                    bestDevice = device;
                }
            }
        }


        if (bestDevice != VK_NULL_HANDLE) {
            physicalDevice = bestDevice;
            VkPhysicalDeviceProperties bestProperties{};
            vkGetPhysicalDeviceProperties(bestDevice, &bestProperties);
            std::cout << "chose device : " << bestProperties.deviceName << std::endl;
        } else {
            throw std::runtime_error("faild got device physical");
        }
    }

    void Window::createLogicalDevice() {

        QueueFamilyIndices indices = findQueueFamilies(physicalDevice);

        std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
        std::set<uint32_t> uniqueQueueFamilies = {
            indices.graphicsFamily.value(),
            indices.presentFamily.value()
        };

        float queuePriority = 1.0f;
        for (uint32_t queueFamily: uniqueQueueFamilies) {
            VkDeviceQueueCreateInfo queueCreateInfo{};
            queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
            queueCreateInfo.queueFamilyIndex = queueFamily;
            queueCreateInfo.queueCount = 1;
            queueCreateInfo.pQueuePriorities = &queuePriority;
            queueCreateInfos.push_back(queueCreateInfo);
        }


        VkPhysicalDeviceFeatures deviceFeatures{};


        VkDeviceCreateInfo createInfo{};
        createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
        createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());

        createInfo.pQueueCreateInfos = queueCreateInfos.data();
        createInfo.pEnabledFeatures = &deviceFeatures;


        createInfo.enabledExtensionCount = 0;

        if (enableValidationLayers) {
            createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
            createInfo.ppEnabledLayerNames = validationLayers.data();
        } else {
            createInfo.enabledLayerCount = 0;
        }
        if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
            throw std::runtime_error("failed to create logical device");
        }
        int deviceValue = (device != VK_NULL_HANDLE) ? reinterpret_cast<uintptr_t>(device) : 0;
        std::cout << "number device : " << deviceValue << std::endl;


        vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
        vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
    }


    void Window::createSurface() {
        if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) {
            throw std::runtime_error("failed to create window surface!");
        }
    }



    bool Window::checkDeviceExtensionSupport(VkPhysicalDevice device) {
        uint32_t extensionCount;
        vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);

        std::vector<VkExtensionProperties> availableExtensions(extensionCount);
        vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());
        std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());
        for (const auto &extension: availableExtensions) {
            requiredExtensions.erase(extension.extensionName);
        }
        return requiredExtensions.empty();
    }



    SwapChainSupportDetails Window::querySwapChainSupport(VkPhysicalDevice device) {
        SwapChainSupportDetails details;
        vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
        uint32_t formatCount;
        vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);
        if (formatCount != 0) {
            details.formats.reserve(formatCount);
            vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
        }

        return details;
    }
};

vulkan one macos have error


Solution

  • You've really answered your own question.

    Yes, Vulkan on macOS with MoltenVk requires the portability subset. This is because there are some things that full Vulkan does that cannot be (efficiently) translated into Metal API calls.

    You need to enable the extension when creating the Vulkan instance by adding the extension name to the list of extensions that are enabled at the instance level.

    There is a code sample showing how to enable it in the Khronos samples repository:

    https://docs.vulkan.org/samples/latest/samples/extensions/portability/README.html