【问题标题】:Nvidia graphics driver causing noticeable frame stutteringNvidia 图形驱动程序导致明显的帧卡顿
【发布时间】:2016-08-25 20:44:59
【问题描述】:

好的,我已经研究这个问题几天了,所以让我回顾一下我到目前为止知道的内容,这让我相信这可能是 NVidia 驱动程序的问题,而不是我的代码.

基本上,我的游戏在运行几秒钟后就开始卡顿(随机帧需要 70 毫秒而不是 16 毫秒,在正常模式下)。仅当在 Nvidia 控制面板(最新驱动程序,Windows 10)中启用名为“线程优化”的设置时才会发生这种情况。不幸的是,此设置默认启用,我不想让人们调整他们的设置来获得愉快的体验。

  • 游戏不是 CPU 或 GPU 密集型游戏(2 毫秒一帧,没有开启垂直同步)。它没有调用任何需要同步数据的 openGL 函数,也没有流式传输任何缓冲区或从 GPU 或任何东西读回数据。关于最简单的渲染器。

  • 问题一直存在,只是在我为音频添加 fmod 时才开始变得明显。 fmod 不是造成这种情况的原因(后面会详细介绍)

    ​​>
  • 尝试使用 NVidia Nsight 调试问题后,问题就消失了。 “开始收集数据”会立即让口吃消失。这里没有骰子。

  • 在 Profiler 中,大量 cpu 时间花在“nvoglv32.dll”中。此过程仅在启用线程优化时才会产生。我怀疑这是一个同步问题,所以我使用 Visual Studio Concurrency Viewer 进行调试。

  • 啊哈!

  • 在 nvidia 线程上调查这些 CPU 时间块,我可以在它们的调用堆栈中得到的最早命名函数是“CreateToolhelp32Snapshot”,然后在Thread32Next 中花费了大量时间。我在前面查看 CPU 时间时注意到分析器中的 Thread32Next,所以这看起来确实是在正确的轨道上。

  • 所以看起来 nvidia 驱动程序出于某种原因会定期抓取整个过程的快照?可能是什么原因,为什么会这样,我该如何阻止它?

  • 这也解释了为什么在我添加 fmod 后问题开始变得明显,因为它会获取所有进程线程的信息,并且 fmod 会产生大量线程。

  • 有什么帮助吗?这只是 nvidia 驱动程序中的一个错误,还是我可以做些什么来修复它,告诉人们禁用线程“优化”?

编辑 1:我的笔记本电脑上当前的 nvidia 驱动程序也会出现同样的问题。所以我没疯

编辑 2:同样的问题出现在 362 版(以前的主要版本)的 nvidia 驱动程序上

【问题讨论】:

  • 嘿,泰勒。 :) 出于好奇-您是否安装了一些调试驱动程序或其他东西?我一辈子都猜不透为什么图形驱动程序需要获取这种信息,除非是出于某种调试/日志记录的原因。
  • 不。它们是 nvidia 网站上公开的驱动程序。我什至不确定从哪里获得调试模式驱动程序...
  • 另外我应该提一下,我也确实在没有运行 Visual Studio 的情况下自行尝试过,以防万一 Visual Studio 正在注入一些调试代码......同样的问题
  • 您是否尝试过为此创建最小复制?它可能会帮助人们帮助你
  • 通常解决这些问题的唯一方法是联系 NVIDIA 开发人员支持 (devrel@nvidia.com?)。由于 NVIDIA 不提供符号 (randomascii.wordpress.com/2011/11/27/a-tale-of-two-call-stacks),并且由于可能的解释集是无限的并且不断变化,因此无法保证您可以自行解决此问题。图形驱动程序似乎是建立在权衡之上的黑客攻击,很容易引发不良行为。祝你好运!

标签: c++ multithreading graphics synchronization nvidia


【解决方案1】:

根据@subGlitch 的回答,以下检查是否已存在应用程序配置文件,如果存在则更新现有配置文件而不是创建新配置文件。它也被封装成一个可以调用的函数,如果在系统上找不到nvidia api(AMD/Intel用户),或者遇到禁止修改配置文件的问题,它将绕过逻辑:

#include <iostream>

#include <nvapi.h>
#include <NvApiDriverSettings.h>


const wchar_t*  profileName = L"Application for testing nvidia api";
const wchar_t*  appName = L"nvapi.exe";
const wchar_t*  appFriendlyName = L"Nvidia api test";
const bool      threadedOptimization = false;


bool nvapiStatusOk(NvAPI_Status status)
{
    if (status != NVAPI_OK)
    {
        // will need to not print these in prod, just return false

        // full list of codes in nvapi_lite_common.h line 249
        std::cout << "Status Code:" << status << std::endl;
        NvAPI_ShortString szDesc = { 0 };
        NvAPI_GetErrorMessage(status, szDesc);
        printf("NVAPI Error: %s\n", szDesc);

        return false;
    }

    return true;
}


void setNVUstring(NvAPI_UnicodeString& nvStr, const wchar_t* wcStr)
{
    for (int i = 0; i < NVAPI_UNICODE_STRING_MAX; i++)
        nvStr[i] = 0;

    int i = 0;
    while (wcStr[i] != 0)
    {
        nvStr[i] = wcStr[i];
        i++;
    }
}

void initNvidiaApplicationProfile()
{
    NvAPI_Status status;

    // if status does not equal NVAPI_OK (0) after initialization,
    // either the system does not use an nvidia gpu, or something went
    // so wrong that we're unable to use the nvidia api...therefore do nothing
    /*
    if (!nvapiStatusOk(NvAPI_Initialize()))
        return;
    */

    // for debugging use ^ in prod
    if (!nvapiStatusOk(NvAPI_Initialize()))
    {
        std::cout << "Unable to initialize Nvidia api" << std::endl;
        return;
    }
    else
    {
        std::cout << "Nvidia api initialized successfully" << std::endl;
    }
        
    // initialize session
    NvDRSSessionHandle hSession;
    if (!nvapiStatusOk(NvAPI_DRS_CreateSession(&hSession)))
        return;

    // load settings
    if (!nvapiStatusOk(NvAPI_DRS_LoadSettings(hSession)))
        return;

    // check if application already exists
    NvDRSProfileHandle hProfile;
    
    NvAPI_UnicodeString nvAppName;
    setNVUstring(nvAppName, appName);

    NVDRS_APPLICATION app;
    app.version = NVDRS_APPLICATION_VER_V1;

    // documentation states this will return ::NVAPI_APPLICATION_NOT_FOUND, however I cannot
    // find where that is defined anywhere in the headers...so not sure what's going to happen with this?
    //
    // This is returning NVAPI_EXECUTABLE_NOT_FOUND, which might be what it's supposed to return when it can't
    // find an existing application, and the documentation is just outdated?
    status = NvAPI_DRS_FindApplicationByName(hSession, nvAppName, &hProfile, &app);
    if (!nvapiStatusOk(status))
    {
        // if status does not equal NVAPI_EXECUTABLE_NOT_FOUND, then something bad happened and we should not proceed
        if (status != NVAPI_EXECUTABLE_NOT_FOUND)
        {
            NvAPI_Unload();
            return;
        }

        // create application as it does not already exist

        // Fill Profile Info
        NVDRS_PROFILE profileInfo;
        profileInfo.version = NVDRS_PROFILE_VER;
        profileInfo.isPredefined = 0;
        setNVUstring(profileInfo.profileName, profileName);

        // Create Profile
        //NvDRSProfileHandle hProfile;
        if (!nvapiStatusOk(NvAPI_DRS_CreateProfile(hSession, &profileInfo, &hProfile)))
        {
            NvAPI_Unload();
            return;
        }

        // Fill Application Info, can't re-use app variable for some reason
        NVDRS_APPLICATION app2;
        app2.version = NVDRS_APPLICATION_VER_V1;
        app2.isPredefined = 0;
        setNVUstring(app2.appName, appName);
        setNVUstring(app2.userFriendlyName, appFriendlyName);
        setNVUstring(app2.launcher, L"");
        setNVUstring(app2.fileInFolder, L"");

        // Create Application
        if (!nvapiStatusOk(NvAPI_DRS_CreateApplication(hSession, hProfile, &app2)))
        {
            NvAPI_Unload();
            return;
        }
    }

    // update profile settings
    NVDRS_SETTING setting;
    setting.version = NVDRS_SETTING_VER;
    setting.settingId = OGL_THREAD_CONTROL_ID;
    setting.settingType = NVDRS_DWORD_TYPE;
    setting.settingLocation = NVDRS_CURRENT_PROFILE_LOCATION;
    setting.isCurrentPredefined = 0;
    setting.isPredefinedValid = 0;
    setting.u32CurrentValue = threadedOptimization ? OGL_THREAD_CONTROL_ENABLE : OGL_THREAD_CONTROL_DISABLE;
    setting.u32PredefinedValue = threadedOptimization ? OGL_THREAD_CONTROL_ENABLE : OGL_THREAD_CONTROL_DISABLE;

    // load settings
    if (!nvapiStatusOk(NvAPI_DRS_SetSetting(hSession, hProfile, &setting)))
    {
        NvAPI_Unload();
        return;
    }

    // save changes
    if (!nvapiStatusOk(NvAPI_DRS_SaveSettings(hSession)))
    {
        NvAPI_Unload();
        return;
    }

    // disable in prod
    std::cout << "Nvidia application profile updated successfully" << std::endl;

    NvAPI_DRS_DestroySession(hSession);

    // unload the api as we're done with it
    NvAPI_Unload();
}

int main()
{
    // if building for anything other than windows, we'll need to not call this AND have
    // some preprocessor logic to not include any of the api code. No linux love apparently...so
    // that's going to be a thing we'll have to figure out down the road -_-
    initNvidiaApplicationProfile();
    
    std::cin.get();
    return 0;
}

【讨论】:

    【解决方案2】:

    首先感谢subGlitch的回答,根据那个建议,我只是做了一个更安全的,它可以让你缓存和更改线程优化,然后再恢复它。

    代码如下:

    #include <stdlib.h>
    #include <stdio.h>
    #include <nvapi.h>
    #include <NvApiDriverSettings.h>
    
    enum NvThreadOptimization {
        NV_THREAD_OPTIMIZATION_AUTO         = 0,
        NV_THREAD_OPTIMIZATION_ENABLE       = 1,
        NV_THREAD_OPTIMIZATION_DISABLE      = 2,
        NV_THREAD_OPTIMIZATION_NO_SUPPORT   = 3
    };
    
    bool NvAPI_OK_Verify(NvAPI_Status status)
    {
        if (status == NVAPI_OK)
            return true;
    
        NvAPI_ShortString szDesc = {0};
        NvAPI_GetErrorMessage(status, szDesc);
    
        char szResult[255];
        sprintf(szResult, "NVAPI error: %s\n\0", szDesc);
        printf(szResult);
    
        return false;
    }
    
    NvThreadOptimization GetNVidiaThreadOptimization()
    {
        NvAPI_Status status;
        NvDRSSessionHandle hSession;
        NvThreadOptimization threadOptimization = NV_THREAD_OPTIMIZATION_NO_SUPPORT;
    
        status = NvAPI_Initialize();
        if(!NvAPI_OK_Verify(status))
            return threadOptimization;
    
        status = NvAPI_DRS_CreateSession(&hSession);
        if(!NvAPI_OK_Verify(status))
            return threadOptimization;
    
        status = NvAPI_DRS_LoadSettings(hSession);
        if(!NvAPI_OK_Verify(status))
        {
            NvAPI_DRS_DestroySession(hSession);
            return threadOptimization;;
        }
    
    
        NvDRSProfileHandle hProfile;
        status = NvAPI_DRS_GetBaseProfile(hSession, &hProfile);
        if(!NvAPI_OK_Verify(status))
        {
            NvAPI_DRS_DestroySession(hSession);
            return threadOptimization;;
        }
    
        NVDRS_SETTING originalSetting;
        originalSetting.version = NVDRS_SETTING_VER;
        status = NvAPI_DRS_GetSetting(hSession, hProfile, OGL_THREAD_CONTROL_ID, &originalSetting);
        if(NvAPI_OK_Verify(status))
        {
            threadOptimization = (NvThreadOptimization)originalSetting.u32CurrentValue;
        }
    
        NvAPI_DRS_DestroySession(hSession);
    
        return threadOptimization;
    }
    
    void SetNVidiaThreadOptimization(NvThreadOptimization threadedOptimization)
    {
        NvAPI_Status status;
        NvDRSSessionHandle hSession;
    
        if(threadedOptimization == NV_THREAD_OPTIMIZATION_NO_SUPPORT)
            return;
    
        status = NvAPI_Initialize();
        if(!NvAPI_OK_Verify(status))
            return;
    
        status = NvAPI_DRS_CreateSession(&hSession);
        if(!NvAPI_OK_Verify(status))
            return;
    
        status = NvAPI_DRS_LoadSettings(hSession);
        if(!NvAPI_OK_Verify(status))
        {
            NvAPI_DRS_DestroySession(hSession);
            return;
        }
    
        NvDRSProfileHandle hProfile;
        status = NvAPI_DRS_GetBaseProfile(hSession, &hProfile);
        if(!NvAPI_OK_Verify(status))
        {
            NvAPI_DRS_DestroySession(hSession);
            return;
        }
    
        NVDRS_SETTING setting;
        setting.version                 = NVDRS_SETTING_VER;
        setting.settingId               = OGL_THREAD_CONTROL_ID;
        setting.settingType             = NVDRS_DWORD_TYPE;
        setting.u32CurrentValue         = (EValues_OGL_THREAD_CONTROL)threadedOptimization;
    
        status = NvAPI_DRS_SetSetting(hSession, hProfile, &setting);
        if(!NvAPI_OK_Verify(status))
        {
            NvAPI_DRS_DestroySession(hSession);
            return;
        }
    
        status = NvAPI_DRS_SaveSettings(hSession);
        NvAPI_OK_Verify(status);
    
        NvAPI_DRS_DestroySession(hSession);
    }
    

    基于上面的两个接口(Get/Set),您可以很好地保存原始设置并在您的应用程序退出时恢复它。这意味着您禁用线程优化的设置只会影响您自己的应用程序。

    static NvThreadOptimization s_OriginalNVidiaThreadOptimization = NV_THREAD_OPTIMIZATION_NO_SUPPORT;
    
    // Set
    s_OriginalNVidiaThreadOptimization =  GetNVidiaThreadOptimization();
    if(    s_OriginalNVidiaThreadOptimization != NV_THREAD_OPTIMIZATION_NO_SUPPORT
        && s_OriginalNVidiaThreadOptimization != NV_THREAD_OPTIMIZATION_DISABLE)
    {
        SetNVidiaThreadOptimization(NV_THREAD_OPTIMIZATION_DISABLE);
    }
    
    //Restore
    if(    s_OriginalNVidiaThreadOptimization != NV_THREAD_OPTIMIZATION_NO_SUPPORT
        && s_OriginalNVidiaThreadOptimization != NV_THREAD_OPTIMIZATION_DISABLE)
    {
        SetNVidiaThreadOptimization(s_OriginalNVidiaThreadOptimization);
    };
    

    【讨论】:

    • 但这不会影响其他在此应用运行时启动的应用吗?
    【解决方案3】:

    ...或者有什么 我可以修复它,其他告诉人们禁用线程 “优化”?

    是的。

    您可以使用NVAPI 为您的游戏创建自定义“应用程序配置文件”并禁用其中的“线程优化”设置。

    在 NVIDIA 网站上有一个 .PDF file,其中包含一些有关 NVAPI 使用的帮助和代码示例。

    为了查看和管理您的所有 NVIDIA 配置文件,我建议使用 NVIDIA Inspector。它比默认的 NVIDIA 控制面板更方便。

    另外,这是我的代码示例,它创建了禁用“线程优化”的“应用程序配置文件”:

    #include <stdlib.h>
    #include <stdio.h>
    
    #include <nvapi.h>
    #include <NvApiDriverSettings.h>
    
    
    const wchar_t*  profileName             = L"Your Profile Name";
    const wchar_t*  appName                 = L"YourGame.exe";
    const wchar_t*  appFriendlyName         = L"Your Game Casual Name";
    const bool      threadedOptimization    = false;
    
    
    void CheckError(NvAPI_Status status)
    {
        if (status == NVAPI_OK)
            return;
    
        NvAPI_ShortString szDesc = {0};
        NvAPI_GetErrorMessage(status, szDesc);
        printf("NVAPI error: %s\n", szDesc);
        exit(-1);
    }
    
    
    void SetNVUstring(NvAPI_UnicodeString& nvStr, const wchar_t* wcStr)
    {
        for (int i = 0; i < NVAPI_UNICODE_STRING_MAX; i++)
            nvStr[i] = 0;
    
        int i = 0;
        while (wcStr[i] != 0)
        {
            nvStr[i] = wcStr[i];
            i++;
        }
    }
    
    
    int main(int argc, char* argv[])
    {
        NvAPI_Status status;
        NvDRSSessionHandle hSession;
    
        status = NvAPI_Initialize();
        CheckError(status);
    
        status = NvAPI_DRS_CreateSession(&hSession);
        CheckError(status);
    
        status = NvAPI_DRS_LoadSettings(hSession);
        CheckError(status);
    
    
        // Fill Profile Info
        NVDRS_PROFILE profileInfo;
        profileInfo.version             = NVDRS_PROFILE_VER;
        profileInfo.isPredefined        = 0;
        SetNVUstring(profileInfo.profileName, profileName);
    
        // Create Profile
        NvDRSProfileHandle hProfile;
        status = NvAPI_DRS_CreateProfile(hSession, &profileInfo, &hProfile);
        CheckError(status);
    
    
        // Fill Application Info
        NVDRS_APPLICATION app;
        app.version                     = NVDRS_APPLICATION_VER_V1;
        app.isPredefined                = 0;
        SetNVUstring(app.appName, appName);
        SetNVUstring(app.userFriendlyName, appFriendlyName);
        SetNVUstring(app.launcher, L"");
        SetNVUstring(app.fileInFolder, L"");
    
        // Create Application
        status = NvAPI_DRS_CreateApplication(hSession, hProfile, &app);
        CheckError(status);
    
    
        // Fill Setting Info
        NVDRS_SETTING setting;
        setting.version                 = NVDRS_SETTING_VER;
        setting.settingId               = OGL_THREAD_CONTROL_ID;
        setting.settingType             = NVDRS_DWORD_TYPE;
        setting.settingLocation         = NVDRS_CURRENT_PROFILE_LOCATION;
        setting.isCurrentPredefined     = 0;
        setting.isPredefinedValid       = 0;
        setting.u32CurrentValue         = threadedOptimization ? OGL_THREAD_CONTROL_ENABLE : OGL_THREAD_CONTROL_DISABLE;
        setting.u32PredefinedValue      = threadedOptimization ? OGL_THREAD_CONTROL_ENABLE : OGL_THREAD_CONTROL_DISABLE;
    
        // Set Setting
        status = NvAPI_DRS_SetSetting(hSession, hProfile, &setting);
        CheckError(status);
    
    
        // Apply (or save) our changes to the system
        status = NvAPI_DRS_SaveSettings(hSession);
        CheckError(status);
    
    
        printf("Success.\n");
    
        NvAPI_DRS_DestroySession(hSession);
    
        return 0;
    }
    

    【讨论】:

    • 感谢代码,如果我的游戏完成时 nvidia 没有修复它,可能最终不得不使用它
    • 请注意,配置文件是在应用程序启动时由驱动程序加载的(从其名称),因此您需要重新启动它才能使您的自定义配置文件生效。在安装程序中这样做可能是最好的。
    • 不应该是NVDRS_APPLICATION_VER而不是NVDRS_APPLICATION_VER_V1吗?
    • @Calvin1602 我的测试似乎表明情况并非如此,并且配置文件实际上是立即激活的。注意:我通过 GLEW 使用 OpenGL,我只在创建配置文件后对其进行初始化。顺便说一句:在创建之前应该使用NvAPI_DRS_FindApplicationByName 检查配置文件是否已经存在。
    【解决方案4】:

    不愿说出显而易见的事,但我觉得有必要说出来。

    线程优化因在许多游戏中导致卡顿而臭名昭著,即使是那些利用多线程的游戏也是如此。除非您的应用程序在线程优化设置下运行良好,否则唯一合乎逻辑的答案是告诉您的用户禁用它。如果用户很固执,不想这样做,那是他们的错。

    最近我能想到的唯一错误是旧版本的 nvidia 驱动程序导致在 Wine 中运行的带有线程优化的应用程序崩溃,但这与您描述的卡顿问题无关。

    【讨论】:

    • 此时,如果这不是 Nvidia 想要解决的问题(我确定了导致口吃的 Windows 功能),我将切换到 Directx,这似乎不是问题存在。
    • “这是他们的错”?!不,如果您的应用程序不起作用,那是您的错。
    猜你喜欢
    • 2011-09-05
    • 2012-05-09
    • 1970-01-01
    • 2021-05-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多