摘要:本文简要描述了Vulkan渲染一个3D模型需要做的事情,不会对太细节的内容进行深究。
关键字:Vulkan,Render,3D
1 简介
1.1 Vulkan简介
Vulkan是一个低开销、跨平台的二维、三维图形与计算的应用程序接口(API),最早由科纳斯组织在2015年游戏开发者大会(GDC)上发表。与OpenGL类似,Vulkan针对全平台即时3D图形程序(如电子游戏和交互媒体)而设计,并提供高性能与更均衡的CPU与GPU占用,这也是Direct3D 12和AMD的Mantle的目标。与Direct3D(12版之前)和OpenGL的其他主要区别是,Vulkan是一个底层API,而且能执行并行任务。除此之外,Vulkan还能更好地分配多个CPU核心的使用。
1.2 OpenGL vs Vulkan
OpenGL | Vulkan |
---|---|
单一的全局状态机 | 基于对象,无全局状态 |
状态与单一上下文绑定 | 所有状态概念都本地化到命令缓冲区 |
操作只能按顺序执行 | 允许多线程编程 |
GPU内存和同步通常是隐藏的 | 显式控制内存管理和同步 |
广泛的错误检查 | Vulkan驱动在运行时不进行错误检查,提供了开发者使用的验证层 |
相比于传统的OpenGL,Vulkan API的设计更加贴近硬件。传统API比如OpenGL内部维护一个单一全局的状态机,这就意味着需要通过一个主线程来处理所有的绘图命令,即便驱动内部能够保证渲染足够高校,但是由于外部提交指令的方式是单线程的容易导致多核CPU的利用率不高。而Vulkan从设计上就考虑了多线程编程,允许开发者在多个线程中并行执行绘图命令和资源管理操作。这样可以大幅提升渲染性能,并使应用程序更具响应性。
另外,传统的API对开发者屏蔽了很多内部细节,这样做的好处是开发者使用起来比较简单,反之缺点是开发者只能通过较高层次的接口进行操作,无法对底层资源进行精细控制,这样可能会导致不必要的性能开销。Vulkan提供了更细粒度的资源管理接口,开发者可以精确控制资源的分配、释放和使用。这样可以减少不必要的资源开销,并允许更高级的优化操作。由于传统API将很多操作限制在了驱动层,驱动程序需要做大量的工作来优化和处理绘图命令,可能带来额外的性能开销。Vulkan通过简化驱动设计,使得驱动更轻量。Vulkan将很多工作(如资源管理和同步)交给开发者处理,这样驱动程序的开销更低,更易于调试和优化。
Vulkan具有更好的扩展性,支持最新的图形硬件特性。Vulkan的设计使得它可以更快速地集成和利用新硬件的功能,这样开发者可以更快地使用新硬件的特性来优化应用程序。由于Vulkan提供更加贴近硬件的API,虽然提升了性能,但是也意味着对开发者要求更高。
从上面的图片可以看出来Vulkan的驱动曾月层要更薄一些。
2 Vulkan
2.1 Instance
Instance是当前渲染环境的上下文,初始化Instance会加载Vulkan驱动程序。一般情况下,一个Vulkan应用程序只需要一个Instance,但在某些特定的场景下多个场景需要独立的渲染上下文时,需要创建多个Instance。比如独立的渲染上下文、不同设备或平台测试、调试和验证、插件或模块系统、多线程初始化以及虚拟化或容器化环境。根据具体需求选择合适的实例创建策略,可以更好地满足应用程序的功能和性能要求。
vk::ApplicationInfo appInfo = {
"Hello Vulkan",
VK_MAKE_VERSION(1, 0, 0),
"Everything but engine",
VK_MAKE_VERSION(1, 0, 0),
VK_API_VERSION_1_0 };
auto glfwExts = Vulkan::QueryGlfwExtension();
vk::InstanceCreateInfo createInfo = {
vk::InstanceCreateFlags{
},
&appInfo,
0,
nullptr,
(uint32_t)glfwExts.size(),
glfwExts.data()
};
if(gEnableValidationLayer){
createInfo.enabledLayerCount = kValidationLayers.size();
createInfo.ppEnabledLayerNames = kValidationLayers.data();
vk::DebugUtilsMessengerCreateInfoEXT debugInfo = {
};
debugInfo.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo
| vk::DebugUtilsMessageSeverityFlagBitsEXT::eError
| vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning;
debugInfo.messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance;
debugInfo.pfnUserCallback = DebugCallback;
createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&DebugCallback;
}
_instance = vk::createInstanceUnique(createInfo, nullptr);
在创建Instance时可以指定期望开启的扩展,毕竟并不是所有的硬件都具有相同的硬件能力,比如只有Nvidia RTX系列有光追能力。下面是我本地机器支持的扩展状态:
The 0 extension,version:1, name:VK_KHR_device_group_creation
The 1 extension,version:23, name:VK_KHR_display
The 2 extension,version:1, name:VK_KHR_external_fence_capabilities
The 3 extension,version:1, name:VK_KHR_external_memory_capabilities
The 4 extension,version:1, name:VK_KHR_external_semaphore_capabilities
The 5 extension,version:1, name:VK_KHR_get_display_properties2
The 6 extension,version:2, name:VK_KHR_get_physical_device_properties2
The 7 extension,version:1, name:VK_KHR_get_surface_capabilities2
The 8 extension,version:25, name:VK_KHR_surface
The 9 extension,version:1, name:VK_KHR_surface_protected_capabilities
The 10 extension,version:6, name:VK_KHR_wayland_surface
The 11 extension,version:6, name:VK_KHR_xcb_surface
The 12 extension,version:6, name:VK_KHR_xlib_surface
The 13 extension,version:1, name:VK_EXT_acquire_drm_display
The 14 extension,version:1, name:VK_EXT_acquire_xlib_display
The 15 extension,version:10, name:VK_EXT_debug_report
The 16 extension,version:2, name:VK_EXT_debug_utils
The 17 extension,version:1, name:VK_EXT_direct_mode_display
The 18 extension,version:1, name:VK_EXT_display_surface_counter
The 19 extension,version:4, name:VK_EXT_swapchain_colorspace
The 20 extension,version:1, name:VK_EXT_surface_maintenance1
The 21 extension,version:1, name:VK_KHR_portability_enumeration
The 22 extension,version:1, name:VK_LUNARG_direct_driver_loading
2.2 校验层
校验层是一个调试工具,用于帮助开发者在开发阶段捕获错误和潜在问题。校验层通过在API调用时执行额外的检查和验证,确保应用程序符合Vulkan规范,并提供有用的调试信息。校验层的启用比较简单,如上面创建Instance时的代码,指定相应参数即可。
2.3 Surface
Surface用于展示画面,虽然主流的渲染都需要显示,但是并不是所有的在渲染都需要在本机器的屏幕上显示,因此Vulkan提供了Surface对应的扩展VK_KHR_surface
。SurfaceKHR(通常简称为Surface)是一个抽象层,用于表示一个窗口系统的表面(Surface),它是Vulkan与窗口系统之间的桥梁。Surface使得Vulkan能够与操作系统的窗口系统交互,从而进行图形渲染。Surface的创建比较简单直接调用Instance的createDisplayPlaneSurfaceKHR
即可,我这里直接使用的GLFW创建的Surface:
void VulkanInstance::createSurface(GLFWwindow *window){
VkSurfaceKHR surface{
};
if(VK_SUCCESS != glfwCreateWindowSurface(*_instance, window, nullptr, &surface)){
throw std::runtime_error("Failed to create window surface");
}
_surface = surface;
}
2.3 设备
Vulkan支持多GPU设备渲染,因此需要我们选择进行渲染硬件设备,而实际使用时并不会直接使用该硬件设备,而是在其基础上创建逻辑设备来使用。通过将逻辑设备和物理设备分开,Vulkan提供了更高级别的抽象和灵活性,使得开发者可以根据具体需求选择和管理硬件资源。逻辑设备负责管理和操作与硬件相关的细节,而物理设备则表示实际的硬件设备。在一个应用程序中,可以创建多个实例(VkInstance),每个实例可以使用多个物理设备(VkPhysicalDevice),基于每个物理设备可以创建多个设备(VkDevice). 目前我们使用的是最为简单的组合:一个实例+一个物理设备+一个设备。
创建物理设备比较简单,首先枚举当前机器中的图形设备,然后根据自己的需求进行筛选:
void VulkanInstance::SelectRunningDevice(){
auto devices = _instance->enumeratePhysicalDevices();
if(devices.empty()){
throw std::runtime_error("No Physical Device found");
}
for(auto &&device : devices){
//LOGI("The {}th device is {}", i, devices[i].)
if(CheckDeviceSuitable(device, _surface)){
_phyDevice = device;
_msaaSamples = GetMaxUsableSampleCount(_phyDevice);
break;//only find one device;
}
}
if(!_phyDevice){
throw std::runtime_error("The device is nullptr, can not find any suitable device");
}
auto phyDevicePro = _phyDevice.getProperties();
LOGI("Select Device Id {} Device name {}", phyDevicePro.deviceID, std::string(phyDevicePro.deviceName));
}
逻辑设备创建过程类似于实例创建过程,并描述期望使用的功能。同时Vulkan设备内部的Queue由驱动层管理,上层只能获取对应的Queue来提交指令,如果期望使用多设备处理,比如一个设备进行计算,另一个设备显示,则可以从不同的设备创建逻辑设备并拿到对应的Queue,分别在对应的Queue上提交指令。下面的实现中PreseneQueue和GraphicsQueue虽然在同一个设备上,但是道理是一样的。
void VulkanInstance::createLogicDevice(){
float priority = 1.0;
auto indics = Utils::Vulkan::QueryQueueFamilyIndices(_phyDevice, _surface);
std::vector<vk::DeviceQueueCreateInfo> queueCreateInfos{
};
std::set<uint32_t> queueFamilies = {
indics.graphics.value(), indics.present.value() };
for(auto fam : queueFamilies){
queueCreateInfos.emplace_back(
vk