[Vulkan教程] 一: 创建VkDevice
2016-05-14 11:39
363 查看
这个系列的文章是写给对已有的D3D11和GL比较熟悉并理解多线程、资源暂存、同步等概念但想进一步了解它们是如何以Vulkan实现的读者。
文章并不追求通俗易懂,里面有很多术语(为了解里面的晦涩内容,建议去阅读Vulkan规范或者更深度的教程)。
为了更好地理解Vulkan的使用,文章会结合笔者正在开发的Vulkan图形库kaleido3d来做说明。
通过VkInstance可以检查GPU设备是否可用。Vulkan不一定是运行在GPU上,但我们把问题简单化。每个GPU都会提供一个句柄 - VkPhysicalDevice。你可以通过它来查询GPU厂商、属性(
一个VkInstance可以有多个VkPhysicalDevice,一个VkPhysicalDevice可以有多个VkDevice。在Vulkan1.0,跨GPU的调用还未实现(将来会)。
大概的初始化调用就像是这样:
Using The Vulkan validation layers
TsinStudio/AndroidDev Post
文章并不追求通俗易懂,里面有很多术语(为了解里面的晦涩内容,建议去阅读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; } }
参考
Vulkan in 30 minutesUsing The Vulkan validation layers
TsinStudio/AndroidDev Post
相关文章推荐
- Vulkan教程
- Vulkan教程-Vulkan实例(代码示例)
- Vulkan示例
- 在 Windows 下构建 LunarG Vulkan Samples
- 在 Windows 下构建 Vulkan LoadAndValidationLayers
- Vulkan Programming Guide 第一章(3)
- Vulkan Programming Guide 第一章(2)
- Multi-Threading in Vulkan
- Vulkan中Loader和Layer的接口(LoaderAndLayerInterface)
- Vulkan Loader Specification and Architecture Overview
- vulkan中对图像image的读写——image view
- vulkan同步机制之——Fence & Barriers
- vulkan起航——调试samples
- 30分钟入门Vulkan
- Vulkan简介
- 没有任何秘密的 API:Vulkan* 简介第 0 部分:前言
- 关于 Vulkan 简介 —— Android N 引入新的 3D 渲染引擎
- Vulkan教程(零)Win32+VS2013环境配置
- OpenGL的替代者——Vulkan
- vulakn教程--Drawing a Triangle--Draw--Render and presentation