【问题标题】:Question about Vulkan Initialization / Deinitialization and Android App life cycle event关于Vulkan初始化/去初始化和Android App生命周期事件的问题
【发布时间】:2019-06-21 03:04:32
【问题描述】:

我研究了很多平台供应商(Qucomm Adreno SDK、PowerVR SDK、ARM Mali SDK 和 Google 的 android NDK 示例)提供的 Vulkan 示例应用程序有一段时间了。我注意到所有示例都在以下代码模式中执行 Vulkan 初始化和反初始化:

void android_main(struct android_app* androidApp)
{
    ...
    androidApp->onAppCmd = [](struct android_app* androidApp, int32_t cmd) -> void //Event handle (Lambda)
    {
        VulkanApp* app = (VulkanApp*)androidApp->userData;
        switch (cmd) 
        {
        case APP_CMD_INIT_WINDOW:   
            initVulkan(...);    //Initialize vulkan: layers, extension, instance, surface, device, swapchain, ...
            break;
        case APP_CMD_TERM_WINDOW:
            deinitVulkan(...);    //Deinitialize vulkan: in reversed order...
            break;
        ...
        }
        ...
    }
    ...
}

基本上应用程序在 NDK 事件 APP_CMD_INIT_WINDOW 中初始化 Vulkan 组件,并在事件 APP_CMD_TERM_WINDOW 中销毁它们。这段代码在用户启动应用,运行一段时间后被用户退出的情况下是相当合理的。


但是,当用户将android应用切换到后台(通过主页或菜单按钮)并多次将其带回来时,将触发配对事件APP_CMD_TERM_WINDOWAPP_CMD_INIT_WINDOW多次,因此函数 initVulkan()deinitVulkan() 将被调用多次。

在这种情况下,代码对我来说似乎是不合理的:由于应用程序只是暂时推到后台并回到前台,我们为什么要销毁所有 Vulkan 组件,如层、扩展、实例、设备、表面、交换链、管道……然后重新创建它们?最多,可能唯一需要重新创建的组件是交换链和管道。但是为什么所有 SDK 的示例应用程序都执行如此繁重的操作呢?

顺便说一句,当我与 Windows、Linux、macOS 和 iOS 等其他平台上的 Vulkan 示例源代码进行比较时,它们都没有进行如此繁重的娱乐。

我曾尝试使用“初始化一次” 解决方案,但是当它从后台返回到前台时,android 应用程序崩溃了。

所以可能的问题是: 当 Android 应用在后台和前台之间切换时,我们是否必须销毁并重新创建所有 Vulkan 组件?如果没有,我们该怎么做?

-----

更新: 我收到的关于我的问题的建议很少,我理解在 Android 应用程序的“交换”期间,我们最好限制应用程序占用的系统资源(特别是在收到低内存警告时),同时,细粒度的暂停Vulkan 组件上的 /resume 机制有助于在轻量内存使用和快速恢复应用之间保持良好的平衡。

我查看了 Google NDK OpenGL ES“茶壶”示例,我注意到这个 NDK gl 示例使用高度查找调整机制来暂停/恢复 OpenGL 上下文: 在事件处理部分,代码如下:

    switch (cmd)
    {
        case APP_CMD_INIT_WINDOW:
            // The window is being shown, get it ready.
            if (app->window != NULL)
                eng->InitDisplay(app);
            break;
        case APP_CMD_TERM_WINDOW:
            // The window is being hidden or closed, clean it up.
            eng->TermDisplay();
            break;
        case APP_CMD_LOW_MEMORY:
            // Free up GL resources
            eng->TrimMemory();
            break;

这是函数 InitDisplay() 的代码:

int Engine::InitDisplay(android_app *app)
{
    if (!initialized_resources_)  // THIS IS FIRST TIME THE EVENT IS TRIGGERED WHEN APP IS LAUNCHED
    {
        gl_context_->Init(app_->window);    //Initialize OpenGL
        LoadResources();
        ...
    }
    else  // TRIGGERED WHEN APP IS BROUGHT BACK FROM BACKGROUND TO FOREGROUND
    {
        // On some devices, ANativeWindow is re-created when the app is resumed
        if (app->window != gl_context_->GetANativeWindow())
        {
            // Re-initialize ANativeWindow.
            assert(gl_context_->GetANativeWindow());
            UnloadResources();
            gl_context_->Invalidate();
            gl_context_->Init(app->window); //Initialize OpenGL again
            LoadResources();
            ...
        }
        // Normal case, only need to resume OpenGL
        else
        {
            // initialize OpenGL ES and EGL
            if (EGL_SUCCESS == gl_context_->Resume(app_->window))//Resume OpenGL
            {
                UnloadResources();
                LoadResources();
            }
            ...
        }
    }
    ...    

从代码中我们可以看出,在大多数“好的情况”下,只有一小部分 OpenGL 资源被卸载和重新加载;只有在“坏情况”下,OpenGL上下文才会被完全销毁并重新创建,这可以产生快速的应用恢复。

所以我的问题可以扩展到: 有没有人知道使用这种 find-grind Vulkan 暂停/恢复机制的优秀 Vulkan/Android 模板应用程序?或者想分享你自己的代码来做到这一点?我目前正在做这件事,但进展并不顺利。

【问题讨论】:

    标签: android vulkan


    【解决方案1】:

    可以在后台保留大部分 Vulkan,但您是对的,您必须重新创建交换链(以及任何依赖对象,例如交换链图像的图像视图),因为您获得了回到前台时新的原生窗口/表面。不幸的是,我没有任何示例代码可以指向您。

    为了简单起见,现有示例可能采用这种方式,因为在大多数其他平台上,不需要支持与其他顶级 Vulkan 对象具有不同生命周期的交换链。

    实际上建议不要在进入后台时立即丢弃太多内存。但是如果你不这样做,你应该注意onTrimMemory回调,当你得到它时释放任何大数据。如果用户短暂切换,这允许您快速恢复(并且不会消耗重新加载纹理等),但仍然允许系统回收内存而不会在需要时完全杀死您的应用程序。

    【讨论】:

    • 谢谢 Jesse,我已经开始让这个应用程序暂停/恢复处理比那些示例应用程序更细粒度。我认为与 java "onTrimMemory()" 等效的 NDK 事件应该是 APP_CMD_LOW_MEMORY,这是 NDK 胶水暴露的一个相关事件。
    • 在对 Google 的 OpenGL 示例应用程序进行审查后,我更新了我的问题,该应用程序提供了微调的 OpenGL 上下文暂停/恢复机制。就个人而言,我认为 Google Vulkan 示例应用程序应该提供类似的微调机制。即使考虑到 sample 的简单意图,对于 Vulkan 初学者来说学习如何进行 Vulkan 资源管理会更好。
    【解决方案2】:

    在这种情况下,代码对我来说似乎是不合理的: 因为该应用程序只是暂时推到后台并带来 回到前台,我们为什么要销毁所有 Vulkan 组件...

    定义“临时”。一个典型的用户可能会打开几十个应用程序,但它们处于空闲状态并在后台运行,如果它们都占用了所有资源,这将消耗大量内存。

    使用图形 API 的应用程序几乎总是占用大量内存,因此期望后台应用程序释放资源供前台应用程序使用是合理的。

    顺便说一下,当我与其他 Vulkan 示例源代码进行比较时 平台,如 Windows、Linux、macOS 和 iOS,它们都不执行 如此繁重的娱乐活动。

    iOS 不强制这样做,但在开发人员最佳实践中强烈建议应用程序释放大量内存资源作为 applicationDidEnterBackground 处理程序的一部分。

    其余的是桌面平台,应用程序使用模型完全不同(打开 -> 关闭,未打开 -> 挂起),因此它们具有不同的编程模型也就不足为奇了。

    【讨论】:

    • 坦克 Solidpixel,我理解你的所有建议。我将通过再次编辑问题进一步扩展我的问题(在这里通过评论扩展它不方便)。不过,我还有一些不太清楚的地方。
    【解决方案3】:

    感谢我收到的一些建议以及this article at Gamasutra,我已经把一切都弄清楚了,终于找到了在应用暂停和恢复期间进行Vulkan资源管理的完美解决方案。

    基本思路是: 应用从挂起状态恢复后,无需重新创建所有 Vulkan 资源。这样做会导致重新创建所有与 Vulkan 相关的应用程序资源,并且很难编码。只需重新创建以下 Vulkan 对象:

    • 表面
    • 渲染通道
    • 交换链及相关

    NDK 事件处理代码应如下所示:

     switch (cmd)
        {
            case APP_CMD_INIT_WINDOW:
                if( this is triggered by app launch)
                    initVulkan();
                else //This is triggered by app resumption
                    resetVulkan(); // recreateSurface, RenderPass, Swapchain and related
                break;
                ...
    

    我的 Vulkan 应用现在可以非常顺利地自行进出。

    【讨论】:

      猜你喜欢
      • 2022-08-03
      • 1970-01-01
      • 2021-12-12
      • 2022-11-01
      • 1970-01-01
      • 2019-02-08
      • 2015-03-22
      • 1970-01-01
      • 2020-12-25
      相关资源
      最近更新 更多