Nvidia图形驱动程序导致明显的框架卡顿

时间:2016-04-30 19:51:24

标签: c++ multithreading graphics synchronization nvidia

好的,我已经研究了这个问题几天了,所以让我回顾一下我知道的到目前为止让我相信这可能是NVidia的一个问题'是司机而不是我的代码。

基本上我的游戏在运行几秒后开始出现口吃(随机帧需要70ms而不是16ms,在规则模式上)。如果设置名为" Threaded Optimization"在Nvidia控制面板中启用(最新驱动程序,Windows 10)。不幸的是,默认情况下启用此设置,而不是让人们调整设置以获得愉快的体验。

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

  • 当我在fmod中添加音频时,问题始终存在,它才开始变得明显。 fmod不是原因(稍后在帖子中)

  • 尝试使用NVidia Nsight调试问题使问题消失。 "开始收集数据"立刻导致口吃消失。这里没有骰子。

  • 在Profiler中,很多cpu时间花费在" nvoglv32.dll"中。如果启用了线程优化,则此过程仅生成。我怀疑这是一个同步问题,因此我使用visual studio Concurrency Viewer进行调试。

  • A-HA! vsyncs

  • 调查nvidia线程上的这些CPU时间块,我可以在其callstack中获得的最早的命名函数是" CreateToolhelp32Snapshot"然后花了很多时间在Thread32Next。我在早期查看CPU时间时发现了探查器中的Thread32Next,所以这看起来好像我在正确的轨道上。

  • 所以看起来周期性的nvidia驱动程序出于某种原因正在抓取整个过程的快照?可能是什么原因,为什么要这样做,我该如何制止呢?

  • 这也解释了为什么一旦我在fmod中添加问题就开始变得明显,因为它抓取了所有进程线程的信息,而fmod产生了很多线程。

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

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

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

4 个答案:

答案 0 :(得分:6)

  

......或者有什么东西   我可以修复它,告诉别人禁用Threaded   “优化”?

您可以使用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;
}

答案 1 :(得分:1)

首先感谢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);
};

答案 2 :(得分:0)

讨厌陈述显而易见但我觉得需要说出来。

线程优化因许多游戏中的口吃而臭名昭着,即使那些利用多线程的游戏也是如此。除非您的应用程序与线程优化设置配合良好,否则唯一合乎逻辑的答案是告诉用户禁用它。如果用户很顽固并且不想这样做,那就是他们的错。

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

答案 3 :(得分:0)

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

#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;
}