狸村 Mystic Web · 狸村
目录

引言

在本章中,我们将探讨 Vulkan Profiles,这是建立在我们上一章讨论的生态工具基础上的一个强大特性。Vulkan Profiles 提供了一种标准化的方式来:

  1. 定义你的应用程序所需的一组特性、扩展和限制

  2. 自动检查与用户硬件的兼容性

  3. 消除手动特性检测和回退路径的需求

  4. 显著减少样板代码

对于希望确保其应用程序在广泛硬件上一致运行,而又不想复杂地手动检查特性支持的开发者来说,Vulkan Profiles 尤其有价值。

理解 Vulkan Profiles

什么是 Vulkan Profiles?

Vulkan Profiles 是特性、扩展、限制和格式的预定义集合,代表特定的目标环境或一组最佳实践。它们在底层 Vulkan API 之上提供了一个更高层次的抽象,使得以下操作更加容易:

  • 针对特定的硬件能力

  • 确保不同 GPU 之间的兼容性

  • 一致地实现最佳实践

  • 减少特性检测的样板代码

你无需手动检查每个特性和扩展并实现回退路径,只需简单指定你的应用程序所需的 Profile。Vulkan Profiles 库将处理兼容性检查,并在用户硬件不满足要求时提供适当的错误信息。

Vulkan Profiles 的类型

有几种类型的 Profile 可用:

  1. API Profiles:代表特定的 Vulkan API 版本(例如,Vulkan 1.1、1.2、1.3)

  2. Vendor Profiles:针对特定的硬件厂商(例如,NVIDIA、AMD、Intel)

  3. Platform Profiles:针对特定的平台(例如,Windows、Linux、Android)

  4. Best Practices Profile:实现 Vulkan 开发的推荐实践

在本章中,我们将以 Best Practices Profile 为例,另外,我们将演示 Profiles 如何通过消除手动特性检测的需求来简化你的代码。

Profiles 如何简化你的代码

消除手动特性检测

到目前为止,我们不得不手动检查特性支持并实现回退路径:

  1. 检查设备是否支持 Vulkan 1.3

  2. 如果不支持,检查它是否支持动态渲染扩展

  3. 如果两者都不支持,回退到传统的渲染通道

  4. 对每个特性(时间线信号量、synchronization2 等)重复此过程

  5. 为每个特性维护单独的代码路径

这种方法导致代码复杂、难以维护,并且有多个条件分支。

使用 Profiles,整个过程简化为:

  1. 检查 Profile 是否受支持

  2. 如果受支持,使用 Profile 保证的所有特性

  3. 如果不支持,可选择回退到更基础的方法

使用 Profiles 的好处

使用 Profiles 有几个优点:

  1. 大幅降低代码复杂度:无需多个特性检查和条件分支

  2. 提高可维护性:需要测试和调试的代码路径更少

  3. 面向未来:随着新 Vulkan 版本的发布,无需更改代码即可更新 Profiles

  4. 更清晰的需求:Profiles 提供了你的应用程序需要的明确规范

  5. 简化的错误处理:一次检查代替多次检查

在你的应用程序中实现 Profiles

让我们看看如何在你的 Vulkan 应用程序中实现 Profiles。我们将以 Best Practices Profile 为例,演示 Profiles 如何替代我们在上一章中不得不做的手动特性检测。

添加 Vulkan Profiles 库

首先,你需要包含 Vulkan Profiles 头文件:

#include <vulkan/vulkan_profiles.hpp>

此头文件提供了使用 Vulkan Profiles 所需的函数和结构体。

Vulkan Profiles 头文件不是标准 Vulkan 头文件的一部分。只有使用 Vulkan SDK 时才可用。请确保你已在开发环境中安装并正确配置了 Vulkan SDK。

定义 Profile 需求

无需手动检查特性和扩展,你可以定义你的 Profile 需求:

// 定义我们要使用的 Profile
const VpProfileProperties profile = {
    VP_KHR_ROADMAP_2022_NAME,
    VP_KHR_ROADMAP_2022_SPEC_VERSION
};

// 检查 Profile 是否受支持
VkBool32 supported = false;
vpGetPhysicalDeviceProfileSupport(instance, physicalDevice, &profile, &supported);

if (!supported) {
    throw std::runtime_error("Roadmap 2022 profile is not supported on this device");
}

使用 Profile 创建设备

当创建逻辑设备时,你可以使用 Profile 自动启用所需的特性和扩展:

// 使用 Best Practices Profile 创建设备
VkDeviceCreateInfo deviceCreateInfo = {};
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;

// 设置队列创建信息
// ...

// 将 Best Practices Profile 应用于设备创建
vpCreateDevice(physicalDevice, &deviceCreateInfo, &bestPracticesProfile, nullptr, &device);

这会自动启用 Best Practices Profile 所需的所有特性和扩展,无需手动指定。

使用 Profile 特定的特性

Best Practices Profile 可能会启用你可以在应用程序中使用的特定特性:

// Profile 保证这些特性可用
// 无需检查支持或提供回退路径

// 示例:使用动态渲染(由 Profile 保证)
vk::RenderingAttachmentInfo colorAttachment{
    .imageView = swapChainImageViews[imageIndex],
    .imageLayout = vk::ImageLayout::eAttachmentOptimal,
    .loadOp = vk::AttachmentLoadOp::eClear,
    .storeOp = vk::AttachmentStoreOp::eStore,
    .clearValue = clearColor
};

vk::RenderingInfo renderingInfo{
    .renderArea = {{0, 0}, swapChainExtent},
    .layerCount = 1,
    .colorAttachmentCount = 1,
    .pColorAttachments = &colorAttachment
};

commandBuffer.beginRendering(renderingInfo);
// ... 绘制命令 ...
commandBuffer.endRendering();

使用 Profiles 进行错误处理

使用 Profiles 时,错误处理变得更加直接:

try {
    // 尝试使用 Best Practices Profile 创建设备
    vpCreateDevice(physicalDevice, &deviceCreateInfo, &bestPracticesProfile, nullptr, &device);
} catch (const std::exception& e) {
    // Profile 不受支持,提供用户友好的错误信息
    std::cerr << "Your GPU does not support the required Vulkan features for optimal performance." << std::endl;
    std::cerr << "Error: " << e.what() << std::endl;

    // 可选择尝试使用更基础的 Profile 或优雅退出
    // ...
}

手动特性检测与 Profiles 的对比

让我们比较这两种方法,以了解 Profiles 可以消除多少代码和复杂度:

手动特性检测(上一章)

在上一章中,我们不得不为 每个 我们想使用的特性编写类似这样的代码:

// 检查是否支持动态渲染
bool dynamicRenderingSupported = false;

// 检查 Vulkan 1.3 支持
if (deviceProperties.apiVersion >= VK_API_VERSION_1_3) {
    dynamicRenderingSupported = true;
} else {
    // 在旧版本上检查扩展
    for (const auto& extension : availableExtensions) {
        if (strcmp(extension.extensionName, VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME) == 0) {
            dynamicRenderingSupported = true;
            break;
        }
    }
}

// 存储此信息供后续使用
appInfo.dynamicRenderingSupported = dynamicRenderingSupported;

然后我们不得不在整个应用程序中创建条件代码路径:

// 创建管线时
if (appInfo.dynamicRenderingSupported) {
    // 使用动态渲染
    vk::PipelineRenderingCreateInfo renderingInfo{
        .colorAttachmentCount = 1,
        .pColorAttachmentFormats = &swapChainImageFormat
    };
    pipelineInfo.pNext = &renderingInfo;
    pipelineInfo.renderPass = nullptr;
} else {
    // 使用传统渲染通道
    pipelineInfo.pNext = nullptr;
    pipelineInfo.renderPass = renderPass;
    pipelineInfo.subpass = 0;
}

// 记录命令缓冲时
if (appInfo.dynamicRenderingSupported) {
    // 开始动态渲染
    vk::RenderingAttachmentInfo colorAttachment{/*...*/};
    vk::RenderingInfo renderingInfo{/*...*/};
    commandBuffer.beginRendering(renderingInfo);
} else {
    // 开始传统渲染通道
    vk::RenderPassBeginInfo renderPassInfo{/*...*/};
    commandBuffer.beginRenderPass(renderPassInfo, vk::SubpassContents::eInline);
}

// 在命令缓冲结束时再次
if (appInfo.dynamicRenderingSupported) {
    commandBuffer.endRendering();
} else {
    commandBuffer.endRenderPass();
}

我们不得不为 每个 我们想有条件使用的特性(时间线信号量、synchronization2 等)重复这种模式,导致复杂、分支的代码,难以维护。

使用 Profiles(本章)

使用 Profiles,所有这些复杂度都减少为:

// 定义 Profile
const VpProfileProperties profile = {
    VP_KHR_ROADMAP_2022_NAME,
    VP_KHR_ROADMAP_2022_SPEC_VERSION
};

// 检查 Profile 是否受支持
VkBool32 supported = false;
vpGetPhysicalDeviceProfileSupport(instance, physicalDevice, &profile, &supported);

if (supported) {
    // 使用 Profile 创建设备 - 自动启用所有特性
    vpCreateDevice(physicalDevice, &deviceCreateInfo, &profile, nullptr, &device);

    // 现在我们可以使用 Profile 保证的任何特性而无需检查
    // 例如,动态渲染始终可用:
    vk::RenderingAttachmentInfo colorAttachment{/*...*/};
    vk::RenderingInfo renderingInfo{/*...*/};
    commandBuffer.beginRendering(renderingInfo);
    // ... 绘制命令 ...
    commandBuffer.endRendering();
}

Profile 方法消除了:

  1. 多个特性检测检查

  2. 整个应用程序中的条件代码路径

  3. 在应用程序状态中跟踪特性支持的需求

  4. 维护和测试多个代码路径的复杂性

这使得代码:

  1. 显著更短

  2. 更易于阅读和理解

  3. 更不容易出错

  4. 更易于维护和更新

使用 Profiles 的最佳实践

在使用 Vulkan Profiles 时,考虑以下最佳实践:

  1. 选择正确的 Profile:选择一个与你的应用程序需求匹配但不过度限制的 Profile。

  2. 提供回退选项:如果 Best Practices Profile 不受支持,考虑回退到更基础的 Profile。

  3. 清晰地传达需求:根据你支持的 Profiles 通知用户硬件要求。

  4. 在各种硬件上测试:即使使用 Profiles,在不同的 GPU 上测试你的应用程序仍然很重要。

  5. 保持更新:Profiles 随新的 Vulkan 版本而演变,因此保持你的实现是最新的。

结论

Vulkan Profiles 提供了一种强大的方式来简化你的 Vulkan 代码,消除手动特性检测和条件代码路径的需求。正如我们在本章中所看到的,Profiles 可以大幅减少你需要编写和维护的代码量,使你的应用程序:

  1. 更简洁、更可读

  2. 更易于维护和更新

  3. 更不容易出错

  4. 在不同硬件上更一致

我们在本章中探讨的示例演示了 Profiles 如何替代我们在上一章中不得不实现的复杂特性检测和回退路径。通过使用 Profiles,你可以更多地关注应用程序的核心功能,而少操心硬件兼容性的复杂性。

Vulkan 是 Khronos Group Inc. 的注册商标

教程内容版权归原作者,遵循 CC BY-SA 4.0;本站独立代码及设计除外。