[Vulkan教程] 一: 创建VkDevice

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tomicyo/article/details/51404932

这个系列的文章是写给对已有的D3D11和GL比较熟悉并理解多线程、资源暂存、同步等概念但想进一步了解它们是如何以Vulkan实现的读者。

文章并不追求通俗易懂,里面有很多术语(为了解里面的晦涩内容,建议去阅读Vulkan规范或者更深度的教程)。

为了更好地理解Vulkan的使用,文章会结合笔者正在开发的Vulkan图形库kaleido3d来做说明。

第一步:创建VkInstance


Vulkan API的初始化必须要创建实例(VkInstance)。Vulkan实例之间是相互独立的,所以你可以给不同的实例设置不同的属性(例如,是否激活validation layer和extensions)。

代码示例:创建VkInstance,为了方便调试,Debug版本加上了API校验。
#if _DEBUG
const char* DebugLevelString(VkDebugReportFlagsEXT lev)
{
    switch (lev)
    {
#define STR(r) case VK_DEBUG_REPORT_ ##r ##_BIT_EXT: return #r
        STR(INFORMATION);
        STR(WARNING);
        STR(PERFORMANCE_WARNING);
        STR(ERROR);
        STR(DEBUG);
#undef STR
    default:
        return "UNKNOWN_ERROR";
    }
}

VKAPI_ATTR VkBool32 VKAPI_CALL MyDebugReportCallback(
    VkDebugReportFlagsEXT       flags,
    VkDebugReportObjectTypeEXT  objectType,
    uint64_t                    object,
    size_t                      location,
    int32_t                     messageCode,
    const char*                 pLayerPrefix,
    const char*                 pMessage,
    void*                       pUserData)
{
    Log::Out(LogLevel::Info, "VkDebug", "{%s}[%s]: %s ", DebugLevelString(flags), pLayerPrefix, pMessage);
    return VK_FALSE;
}

static VkDebugReportCallbackEXT callback;
#endif

VkResult RHIRoot::Initializer::Init(bool enableValidation, std::string name)
{
    VkResult err;
    err = CreateInstance(enableValidation, name);
    if (err)
    {
        Log::Out(LogLevel::Fatal, "RHIRoot::Initializer", "Could not create Vulkan instance : %s.", vkTools::errorString(err).c_str());
    }
    else
    {
        Log::Out(LogLevel::Info, "RHIRoot::Initializer", "Vulkan instance created. version %d.%d.%d", VK_VERSION_MAJOR(VK_API_VERSION), VK_VERSION_MINOR(VK_API_VERSION), VK_VERSION_PATCH(VK_API_VERSION));
    }

#if _DEBUG
    /* Load VK_EXT_debug_report entry points in debug builds */
    PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT =
        reinterpret_cast<PFN_vkCreateDebugReportCallbackEXT>
        (vkGetInstanceProcAddr(Instance, "vkCreateDebugReportCallbackEXT"));
    PFN_vkDebugReportMessageEXT vkDebugReportMessageEXT =
        reinterpret_cast<PFN_vkDebugReportMessageEXT>
        (vkGetInstanceProcAddr(Instance, "vkDebugReportMessageEXT"));
    PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallbackEXT =
        reinterpret_cast<PFN_vkDestroyDebugReportCallbackEXT>
        (vkGetInstanceProcAddr(Instance, "vkDestroyDebugReportCallbackEXT"));
    VkDebugReportCallbackCreateInfoEXT callbackCreateInfo;
    callbackCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
    callbackCreateInfo.pNext = nullptr;
    callbackCreateInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT |
        VK_DEBUG_REPORT_WARNING_BIT_EXT |
        VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT;
    callbackCreateInfo.pfnCallback = &MyDebugReportCallback;
    callbackCreateInfo.pUserData = nullptr;

    VkResult result = vkCreateDebugReportCallbackEXT(Instance, &callbackCreateInfo, nullptr, &callback);
#endif

    uint32_t gpuCount;
    err = vkEnumeratePhysicalDevices(Instance, &gpuCount, nullptr);
    std::vector<VkPhysicalDevice> deviceList(gpuCount);
    err = vkEnumeratePhysicalDevices(Instance, &gpuCount, deviceList.data());
    Log::Out(LogLevel::Info, "RHIRoot::Initializer", "Device Count : %d", gpuCount);
    PhysicalDevices.swap(deviceList);
    return err;
}

VkResult RHIRoot::Initializer::CreateInstance(bool enableValidation, std::string name)
{
    VkApplicationInfo appInfo = {};
    appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
    appInfo.pApplicationName = name.c_str();
    appInfo.pEngineName = name.c_str();
    appInfo.apiVersion = VK_API_VERSION;
    std::vector<const char*> enabledExtensions = { VK_KHR_SURFACE_EXTENSION_NAME };

#if K3DPLATFORM_OS_WIN
    enabledExtensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
#else
    // todo : linux/android
    enabledExtensions.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME);
#endif

    VkInstanceCreateInfo instanceCreateInfo = {};
    instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    instanceCreateInfo.pNext = NULL;
    instanceCreateInfo.pApplicationInfo = &appInfo;
    if (enabledExtensions.size() > 0)
    {
        if (enableValidation)
        {
            enabledExtensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
        }
        instanceCreateInfo.enabledExtensionCount = (uint32_t)enabledExtensions.size();
        instanceCreateInfo.ppEnabledExtensionNames = enabledExtensions.data();
    }
    if (enableValidation)
    {
        instanceCreateInfo.enabledLayerCount = vkDebug::validationLayerCount;
        instanceCreateInfo.ppEnabledLayerNames = vkDebug::validationLayerNames;
    }
    return vkCreateInstance(&instanceCreateInfo, nullptr, &Instance);
}

void RHIRoot::Initializer::Destroy()
{
#if _DEBUG
    PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallbackEXT =
        reinterpret_cast<PFN_vkDestroyDebugReportCallbackEXT>
        (vkGetInstanceProcAddr(Instance, "vkDestroyDebugReportCallbackEXT"));
    vkDestroyDebugReportCallbackEXT(Instance, callback, nullptr);
#endif
    vkDestroyInstance(Instance, nullptr);
    Log::Out(LogLevel::Info, "RHIRoot::Initializer", "Destroy Instance.");
}

通过VkInstance可以检查GPU设备是否可用。Vulkan不一定是运行在GPU上,但我们把问题简单化。每个GPU都会提供一个句柄 - VkPhysicalDevice。你可以通过它来查询GPU厂商、属性(vkGetPhysicalDeviceProperties)、能力(vkGetPhysicalDeviceFeatures)等。

代码示例: 枚举所有可用的GPU设备
void EnumAllDeviceAdapter(rhi::IDeviceAdapter** & adapterList, uint32* count)
{
    *count = (uint32)RHIRoot::GetPhysicDevices().size();
    adapterList = new rhi::IDeviceAdapter*[*count];
    for (size_t i = 0; i < *count; i++)
    {
        VkPhysicalDeviceProperties properties;
        vkGetPhysicalDeviceProperties(RHIRoot::GetPhysicDevices()[i], &properties);
        adapterList[i] = new DeviceAdapter(&(RHIRoot::GetPhysicDevices()[i]));
        Log::Out(LogLevel::Info, "EnumAllDeviceAdapter", "DeviceName is %s, VendorId is %d.", properties.deviceName, properties.vendorID);
    }
}

第二步:创建VkDevice


通过VkPhysicalDevice可以创建VkDevice。VkDevice是主要的调用句柄,它在逻辑上与GPU相联系。它相当于GL Context或者D3D11 Device。

一个VkInstance可以有多个VkPhysicalDevice,一个VkPhysicalDevice可以有多个VkDevice。在Vulkan1.0,跨GPU的调用还未实现(将来会)。

大概的初始化调用就像是这样:vkCreateInstance()vkEnumeratePhysicalDevices()vkCreateDevice() 。对于一个简陋的HelloTriangle程序来说,你只需要简单地将第一个物理设备作为主要的VkDevice,然后打开这个设备的相应属性(错误上报、API调用检查)进行开发就行了。

代码示例:
rhi::IDevice::Result
Device::Create(rhi::IDeviceAdapter* pAdapter, bool withDbg)
{
    m_pPhysicDevice = static_cast<DeviceAdapter*>(pAdapter)->m_pPDevice;
    VkPhysicalDevice& physicalDevice = *static_cast<DeviceAdapter*>(pAdapter)->m_pPDevice;
    uint32_t graphicsQueueIndex = 0;
    vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &m_QueueCount, NULL);
    K3D_ASSERT(m_QueueCount >= 1);
    Log::Out(LogLevel::Info, "Device", "PhysicDeviceQueue count = %d.", m_QueueCount);
    std::vector<VkQueueFamilyProperties> queueProps;
    queueProps.resize(m_QueueCount);
    vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &m_QueueCount, queueProps.data());
    for (graphicsQueueIndex = 0; graphicsQueueIndex < m_QueueCount; graphicsQueueIndex++)
    {
        if (queueProps[graphicsQueueIndex].queueFlags & VK_QUEUE_GRAPHICS_BIT)
        {
            m_GfxQueueIndex = graphicsQueueIndex;
            Log::Out(LogLevel::Info, "Device", "graphicsQueueIndex(%d) queueFlags(%d).", graphicsQueueIndex, queueProps[graphicsQueueIndex].queueFlags);
            break;
        }
    }
    K3D_ASSERT(graphicsQueueIndex < m_QueueCount);

    std::array<float, 1> queuePriorities = { 0.0f };
    VkDeviceQueueCreateInfo queueCreateInfo = {};
    queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
    queueCreateInfo.queueFamilyIndex = graphicsQueueIndex;
    queueCreateInfo.queueCount = 1;
    queueCreateInfo.pQueuePriorities = queuePriorities.data();

    std::vector<const char*> enabledExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
    VkDeviceCreateInfo deviceCreateInfo = {};
    deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
    deviceCreateInfo.pNext = NULL;
    deviceCreateInfo.queueCreateInfoCount = 1;
    deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo;
    deviceCreateInfo.pEnabledFeatures = NULL;

#if _DEBUG
    deviceCreateInfo.enabledLayerCount = vkDebug::validationLayerCount;
    deviceCreateInfo.ppEnabledLayerNames = vkDebug::validationLayerNames;
#endif

    if (enabledExtensions.size() > 0)
    {
        deviceCreateInfo.enabledExtensionCount = (uint32_t)enabledExtensions.size();
        deviceCreateInfo.ppEnabledExtensionNames = enabledExtensions.data();
    }
    VkResult err = vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &m_Device);
    vkGetPhysicalDeviceMemoryProperties(physicalDevice, &m_MemoryProperties);

    if (err)
    {
        Log::Out(LogLevel::Fatal, "Device", "Create: Could not create Vulkan Device : %s.", vkTools::errorString(err).c_str());
        return rhi::IDevice::DeviceNotFound;
    }
    else {
        m_ResourceManager.swap(std::make_unique<ResourceManager>(this, 1024, 1024));
        m_ContextPool.swap(std::make_unique<CommandContextPool>(this));
        InitCmdQueue(VK_QUEUE_GRAPHICS_BIT, graphicsQueueIndex, 0);
        return rhi::IDevice::DeviceFound;
    }
}

参考

  1. Vulkan in 30 minutes
  2. Using The Vulkan validation layers
  3. TsinStudio/AndroidDev Post

猜你喜欢

转载自blog.csdn.net/tomicyo/article/details/51404932