探索 Vulkan 音视频技术(11):平台特定实现

这个系列文章我们来介绍一位海外工程师如何探索 Vulkan 音视频技术,对于想要开始学习音视频技术的朋友,这些文章是份不错的入门资料,本篇介绍平台特定实现。

——来自公众号“关键帧Keyframe”的分享

在开发跨平台 Vulkan 应用程序时,处理平台特定差异对于确保代码在不同操作系统上无缝运行至关重要。SaschaWillems/Vulkan 仓库展示了一种优雅的平台抽象方法,使相同的 Vulkan 示例能够在 Windows、Linux、Android、iOS 和 macOS 上运行,且只需极少的平台特定代码。

该仓库采用分层架构,将平台特定实现与核心 Vulkan 逻辑隔离。这种模式允许开发者编写一次 Vulkan 代码,即可在多个平台上部署,而无需进行大量修改。关键洞察在于,虽然 Vulkan 本身是跨平台的,但周边基础设施——窗口创建、输入处理和表面创建——在不同平台间差异显著。

该架构的核心是 VulkanExampleBase 类,它为所有平台特定实现提供统一接口。每个平台然后提供自己的包装器,将平台特定事件和操作转换为基类期望的通用接口。

来源:vulkanexamplebase.h, vulkanexamplebase.cpp

1、Android 实现:动态加载和原生集成

Android 为 Vulkan 开发带来了独特挑战,主要由于需要动态库加载和 Android 原生 Activity 生命周期。该仓库通过全面的 Android 特定实现解决了这些挑战。

1.1、动态 Vulkan 库加载

与桌面平台通常静态链接 Vulkan 库不同,Android 需要动态加载 Vulkan 库。VulkanAndroid.cpp 实现展示了这种模式:

bool loadVulkanLibrary()
{
    // 加载 vulkan 库
    libVulkan = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
    if (!libVulkan)
    {
        __android_log_print(ANDROID_LOG_INFO, "vulkanandroid", "无法加载 vulkan 库 : %s!\n", dlerror());
        return false;
    }

    // 加载基础函数指针
    vkEnumerateInstanceExtensionProperties = reinterpret_cast<PFN_vkEnumerateInstanceExtensionProperties>(dlsym(libVulkan, "vkEnumerateInstanceExtensionProperties"));
    // ... 更多函数指针加载
}

这种方法确保与可能没有内置 Vulkan 支持的旧版 Android 设备兼容,同时为较新设备保持灵活性。

1.2、Android 特定函数指针

Android 实现在头文件中显式声明所有必要的 Vulkan 函数指针。这是必要的,因为 Android 不提供静态 Vulkan 加载器,因此必须动态解析所有函数指针:

extern PFN_vkCreateInstance vkCreateInstance;
extern PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr;
extern PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
// ... 更多函数指针声明

这种模式确保所有 Vulkan 功能都可通过动态加载的符号使用,使代码兼容 Android 的安全模型和库加载要求。

来源:VulkanAndroid.h#L42-L165

1.3、Android 输入和生命周期处理

Android 实现还提供用于处理平台特定功能(如触摸输入、屏幕密度检测和警告对话框)的实用程序。getDeviceConfig() 函数展示了如何检索 Android 特定设备信息:

void getDeviceConfig()
{
    // 屏幕密度
    AConfiguration* config = AConfiguration_new();
    AConfiguration_fromAssetManager(config, androidApp->activity->assetManager);
    vks::android::screenDensity = AConfiguration_getDensity(config);
    AConfiguration_delete(config);
}

这些信息对于正确缩放 UI 元素和处理 Android 设备间不同屏幕分辨率至关重要。

来源:VulkanAndroid.cpp#L339-L346

2、Apple 实现:MoltenVK 集成

对于 Apple 平台(iOS 和 macOS),该仓库使用 MoltenVK——一种 Vulkan 实现,将 Vulkan API 调用转换为 Apple 的 Metal 图形框架。这种方法允许 Vulkan 应用程序在 Apple 设备上运行,而无需 Metal 特定代码。

2.1、MVKExample 包装器类

MVKExample 类充当 Apple 特定 UI 框架与跨平台 Vulkan 代码之间的桥梁:

class MVKExample {
public:
    void displayLinkOutputCb();                     // 公开渲染回调
    void keyPressed(uint32_t keyChar);              // 处理键盘事件
    void mouseDown(double x, double y);             // 处理鼠标事件
    // ... 更多事件处理方法

    MVKExample(void* view, double scaleUI);        // 使用视图和 UI 缩放初始化
    ~MVKExample();

protected:
    VulkanExampleBase* _vulkanExample;             // 指向基础实现的指针
};

此包装器将 Apple 特定事件(触摸、手势、键盘输入)转换为 VulkanExampleBase 类期望的统一接口。

来源:MVKExample.h#L13-L38

2.2、iOS 特定触摸和手势处理

iOS 实现展示了复杂的触摸和手势映射,以模拟 Vulkan 示例的鼠标输入:

// 处理触摸事件
-(void) touchesBegan:(NSSet*) touches withEvent:(UIEvent*) theEvent {
    if (touches.count == 1) {
        auto point = [self getTouchLocalPoint: theEvent];
        _mvkExample->mouseDown(point.x, point.y);
    }
}

// 处理缩放手势
-(void) handlePinchGesture: (UIPinchGestureRecognizer*) gestureRecognizer {
    switch (gestureRecognizer.state) {
        case UIGestureRecognizerStateBegan: {
            _startPoint = [self getGestureLocalPoint: gestureRecognizer];
            _mvkExample->rightMouseDown(_startPoint.x, _startPoint.y);
            break;
        }
        case UIGestureRecognizerStateChanged: {
            _mvkExample->mouseDragged(_startPoint.x, _startPoint.y - self.view.frame.size.height * log(gestureRecognizer.scale));
            break;
        }
        default: {
            _mvkExample->rightMouseUp();
            break;
        }
    }
}

这种方法将 iOS 触摸手势映射到鼠标事件,允许在桌面平台上使用鼠标输入的相同 Vulkan 代码在 iOS 设备上通过触摸输入无缝运行。

来源:DemoViewController.mm#L154-L170, DemoViewController.mm#L208-L224

2.3、Metal 层集成

iOS 实现创建了一个与 Metal 兼容的层,MoltenVK 使用该层进行渲染:

@implementation DemoView

/** 返回与 Metal 兼容的层。 */
+(Class) layerClass { return [CAMetalLayer class]; }

@end

这个简单但关键的覆盖确保视图提供 Metal 层,这是 MoltenVK 在 iOS 设备上创建 Vulkan 表面所必需的。

来源:DemoViewController.mm#L232-L237

3、平台特定资产管理

不同平台以不同方式处理资源加载,该仓库为此常见需求提供平台特定实现:

3.1、iOS 资源路径解析

const std::string getAssetPath() {
    return [NSBundle.mainBundle.resourcePath stringByAppendingString: @"/assets/"].UTF8String;
}

const std::string getShaderBasePath() {
    return [NSBundle.mainBundle.resourcePath stringByAppendingString: @"/shaders/"].UTF8String;
}

这种方法使用 iOS 的捆绑系统定位资源和着色器,确保它们与应用程序正确打包并在运行时可访问。

来源:DemoViewController.mm#L13-L19

3.2、Android 资产管理

Android 通过原生应用程序胶水访问的资产管理器采用不同方法。Android 实现提供用于访问 APK 文件中打包的资源的实用程序。

4、关键设计原则

该仓库中的平台特定实现遵循几个重要设计原则:

4.1、 关注点分离

平台特定代码与核心 Vulkan 逻辑严格分离。这使得代码库更易于维护和扩展到新平台。

4.2、 一致接口

所有平台实现都为核心 Vulkan 代码提供相同接口,确保示例在平台间一致运行。

4.3、 最小化平台代码

平台特定实现尽可能保持最小,仅关注平台间的基本差异。

4.4、 前向兼容性

实现旨在前向兼容,使添加对新平台的支持或更新现有平台变得更容易。

5、结论

SaschaWillems/Vulkan 仓库中的平台特定实现展示了跨平台 Vulkan 开发的成熟且架构良好的方法。通过将平台特定代码隔离在一致接口之后,并为每个平台使用适当的抽象,该仓库实现了卓越的移植性,同时保持了代码清晰度和可维护性。

这种模式为希望构建跨平台 Vulkan 应用程序的开发者提供了极佳的参考,为动态库加载、输入处理和不同操作系统间的表面创建等常见挑战提供了实用解决方案。

学习和提升音视频开发技术,推荐你加入我们的知识星球:【关键帧的音视频开发圈】

探索 Vulkan 音视频技术(11):平台特定实现

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论